diff options
Diffstat (limited to 'src/test/librbd')
106 files changed, 49175 insertions, 0 deletions
diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt new file mode 100644 index 00000000..39c76678 --- /dev/null +++ b/src/test/librbd/CMakeLists.txt @@ -0,0 +1,175 @@ +set(librbd_test_support_srcs + test_support.cc + ) +add_library(rbd_test_support STATIC ${librbd_test_support_srcs}) +target_link_libraries(rbd_test_support PRIVATE + GTest::GTest) + +set(librbd_test + test_fixture.cc + test_librbd.cc + test_ImageWatcher.cc + test_internal.cc + test_mirroring.cc + test_BlockGuard.cc + test_DeepCopy.cc + test_Groups.cc + test_Migration.cc + test_MirroringWatcher.cc + test_ObjectMap.cc + test_Operations.cc + test_Trash.cc + journal/test_Entries.cc + journal/test_Replay.cc) +add_library(rbd_test STATIC ${librbd_test}) +target_link_libraries(rbd_test PRIVATE + rbd_test_support + radostest + radostest-cxx + librados + Boost::thread + GMock::GMock + GTest::GTest) + +set(librbd_test_mock_srcs + mock/MockImageCtx.cc + mock/MockJournal.cc) +add_library(rbd_test_mock STATIC ${librbd_test_mock_srcs}) +target_link_libraries(rbd_test_mock PUBLIC + GMock::GMock) + +# unittest_librbd +# doesn't use add_ceph_test because it is called by run-rbd-unit-tests.sh +set(unittest_librbd_srcs + test_main.cc + test_mock_fixture.cc + test_mock_ConfigWatcher.cc + test_mock_DeepCopyRequest.cc + test_mock_ExclusiveLock.cc + test_mock_Journal.cc + test_mock_ManagedLock.cc + test_mock_ObjectMap.cc + test_mock_TrashWatcher.cc + test_mock_Watcher.cc + deep_copy/test_mock_ImageCopyRequest.cc + deep_copy/test_mock_MetadataCopyRequest.cc + deep_copy/test_mock_ObjectCopyRequest.cc + deep_copy/test_mock_SetHeadRequest.cc + deep_copy/test_mock_SnapshotCopyRequest.cc + deep_copy/test_mock_SnapshotCreateRequest.cc + 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 + image/test_mock_DetachParentRequest.cc + image/test_mock_ListWatchersRequest.cc + image/test_mock_PreRemoveRequest.cc + image/test_mock_RefreshRequest.cc + image/test_mock_RemoveRequest.cc + image/test_mock_ValidatePoolRequest.cc + io/test_mock_CopyupRequest.cc + io/test_mock_ImageRequest.cc + io/test_mock_ImageRequestWQ.cc + io/test_mock_ObjectRequest.cc + journal/test_mock_OpenRequest.cc + journal/test_mock_PromoteRequest.cc + journal/test_mock_Replay.cc + journal/test_mock_ResetRequest.cc + managed_lock/test_mock_AcquireRequest.cc + managed_lock/test_mock_BreakRequest.cc + managed_lock/test_mock_GetLockerRequest.cc + managed_lock/test_mock_ReacquireRequest.cc + managed_lock/test_mock_ReleaseRequest.cc + mirror/test_mock_DisableRequest.cc + object_map/test_mock_InvalidateRequest.cc + object_map/test_mock_LockRequest.cc + object_map/test_mock_RefreshRequest.cc + object_map/test_mock_ResizeRequest.cc + object_map/test_mock_SnapshotCreateRequest.cc + object_map/test_mock_SnapshotRemoveRequest.cc + object_map/test_mock_SnapshotRollbackRequest.cc + object_map/test_mock_UnlockRequest.cc + object_map/test_mock_UpdateRequest.cc + operation/test_mock_DisableFeaturesRequest.cc + operation/test_mock_EnableFeaturesRequest.cc + operation/test_mock_Request.cc + operation/test_mock_ResizeRequest.cc + operation/test_mock_SnapshotCreateRequest.cc + operation/test_mock_SnapshotProtectRequest.cc + operation/test_mock_SnapshotRemoveRequest.cc + operation/test_mock_SnapshotRollbackRequest.cc + operation/test_mock_SnapshotUnprotectRequest.cc + operation/test_mock_TrimRequest.cc + trash/test_mock_MoveRequest.cc + trash/test_mock_RemoveRequest.cc + watcher/test_mock_RewatchRequest.cc + ) +add_executable(unittest_librbd + ${unittest_librbd_srcs} + $<TARGET_OBJECTS:common_texttable_obj>) +target_compile_definitions(unittest_librbd PRIVATE "TEST_LIBRBD_INTERNALS") +target_link_libraries(unittest_librbd + cls_rbd + cls_rbd_client + cls_lock + cls_lock_client + journal + journal_test_mock + cls_journal + cls_journal_client + rados_test_stub + librados + rbd_test + rbd_test_mock + rbd_api + rbd_internal + rbd_types + osdc + ceph-common + global + ${UNITTEST_LIBS}) + +add_executable(ceph_test_librbd + test_main.cc + $<TARGET_OBJECTS:common_texttable_obj>) +target_link_libraries(ceph_test_librbd + rbd_test + rbd_api + rbd_internal + rbd_types + journal + cls_journal_client + cls_rbd_client + librados + ${UNITTEST_LIBS} + radostest) +target_compile_definitions(ceph_test_librbd PRIVATE "TEST_LIBRBD_INTERNALS") + +add_executable(ceph_test_librbd_fsx + fsx.cc + $<TARGET_OBJECTS:common_texttable_obj> + ) +target_link_libraries(ceph_test_librbd_fsx + librbd + librados + journal + global + m + ${CMAKE_DL_LIBS} + ${CRYPTO_LIBS} + ${EXTRALIBS} + ) +if(WITH_KRBD) + target_link_libraries(ceph_test_librbd_fsx + krbd) +endif() +install(TARGETS + ceph_test_librbd_fsx + DESTINATION ${CMAKE_INSTALL_BINDIR}) + +install(TARGETS + ceph_test_librbd + DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc b/src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc new file mode 100644 index 00000000..d6cc03a5 --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc @@ -0,0 +1,583 @@ +// -*- 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 "include/rbd/librbd.hpp" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/deep_copy/ImageCopyRequest.h" +#include "librbd/deep_copy/ObjectCopyRequest.h" +#include "librbd/image/CloseRequest.h" +#include "librbd/image/OpenRequest.h" +#include "librbd/internal.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/test_support.h" +#include <boost/scope_exit.hpp> + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + static MockTestImageCtx* s_instance; + static MockTestImageCtx* create(const std::string &image_name, + const std::string &image_id, + librados::snap_t snap_id, librados::IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + explicit MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + s_instance = this; + } + + MOCK_METHOD0(destroy, void()); +}; + +MockTestImageCtx* MockTestImageCtx::s_instance = nullptr; + +} // anonymous namespace + +namespace deep_copy { + +template <> +struct ObjectCopyRequest<librbd::MockTestImageCtx> { + static ObjectCopyRequest* s_instance; + static ObjectCopyRequest* create( + librbd::MockTestImageCtx *src_image_ctx, + librbd::MockTestImageCtx *dst_image_ctx, + librados::snap_t src_snap_id_start, + librados::snap_t dst_snap_id_start, + const SnapMap &snap_map, + uint64_t object_number, bool flatten, Context *on_finish) { + ceph_assert(s_instance != nullptr); + Mutex::Locker locker(s_instance->lock); + s_instance->snap_map = &snap_map; + s_instance->object_contexts[object_number] = on_finish; + s_instance->cond.Signal(); + return s_instance; + } + + MOCK_METHOD0(send, void()); + + Mutex lock; + Cond cond; + + const SnapMap *snap_map = nullptr; + std::map<uint64_t, Context *> object_contexts; + + ObjectCopyRequest() : lock("lock") { + s_instance = this; + } +}; + +ObjectCopyRequest<librbd::MockTestImageCtx>* ObjectCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace deep_copy + +namespace image { + +template <> +struct CloseRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static CloseRequest* s_instance; + static CloseRequest* create(MockTestImageCtx *image_ctx, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + CloseRequest() { + s_instance = this; + } +}; + +CloseRequest<MockTestImageCtx>* CloseRequest<MockTestImageCtx>::s_instance = nullptr; + +template <> +struct OpenRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static OpenRequest* s_instance; + static OpenRequest* create(MockTestImageCtx *image_ctx, + 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()); + + OpenRequest() { + s_instance = this; + } +}; + +OpenRequest<MockTestImageCtx>* OpenRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace image + +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/ImageCopyRequest.cc" +template class librbd::deep_copy::ImageCopyRequest<librbd::MockTestImageCtx>; + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; + +class TestMockDeepCopyImageCopyRequest : public TestMockFixture { +public: + typedef ImageCopyRequest<librbd::MockTestImageCtx> MockImageCopyRequest; + typedef ObjectCopyRequest<librbd::MockTestImageCtx> MockObjectCopyRequest; + + librbd::ImageCtx *m_src_image_ctx; + librbd::ImageCtx *m_dst_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + librbd::SnapSeqs m_snap_seqs; + SnapMap m_snap_map; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_src_image_ctx)); + + librbd::RBD rbd; + std::string dst_image_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(rbd, m_ioctx, dst_image_name, m_image_size)); + ASSERT_EQ(0, open_image(dst_image_name, &m_dst_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_src_image_ctx->cct, + &m_thread_pool, &m_work_queue); + } + + void expect_get_image_size(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t size) { + EXPECT_CALL(mock_image_ctx, get_image_size(_)) + .WillOnce(Return(size)).RetiresOnSaturation(); + } + + void expect_object_copy_send(MockObjectCopyRequest &mock_object_copy_request) { + EXPECT_CALL(mock_object_copy_request, send()); + } + + bool complete_object_copy(MockObjectCopyRequest &mock_object_copy_request, + uint64_t object_num, Context **object_ctx, int r) { + Mutex::Locker locker(mock_object_copy_request.lock); + while (mock_object_copy_request.object_contexts.count(object_num) == 0) { + if (mock_object_copy_request.cond.WaitInterval(mock_object_copy_request.lock, + utime_t(10, 0)) != 0) { + return false; + } + } + + if (object_ctx != nullptr) { + *object_ctx = mock_object_copy_request.object_contexts[object_num]; + } else { + m_work_queue->queue(mock_object_copy_request.object_contexts[object_num], + r); + } + return true; + } + + SnapMap wait_for_snap_map(MockObjectCopyRequest &mock_object_copy_request) { + Mutex::Locker locker(mock_object_copy_request.lock); + while (mock_object_copy_request.snap_map == nullptr) { + if (mock_object_copy_request.cond.WaitInterval(mock_object_copy_request.lock, + utime_t(10, 0)) != 0) { + return SnapMap(); + } + } + return *mock_object_copy_request.snap_map; + } + + int create_snap(librbd::ImageCtx *image_ctx, const char* snap_name, + librados::snap_t *snap_id) { + int r = image_ctx->operations->snap_create( + cls::rbd::UserSnapshotNamespace(), snap_name); + if (r < 0) { + return r; + } + + r = image_ctx->state->refresh(); + if (r < 0) { + return r; + } + + if (image_ctx->snap_ids.count({cls::rbd::UserSnapshotNamespace(), + snap_name}) == 0) { + return -ENOENT; + } + + if (snap_id != nullptr) { + *snap_id = image_ctx->snap_ids[{cls::rbd::UserSnapshotNamespace(), + snap_name}]; + } + return 0; + } + + int create_snap(const char* snap_name, + librados::snap_t *src_snap_id_ = nullptr) { + librados::snap_t src_snap_id; + int r = create_snap(m_src_image_ctx, snap_name, &src_snap_id); + if (r < 0) { + return r; + } + + if (src_snap_id_ != nullptr) { + *src_snap_id_ = src_snap_id; + } + + librados::snap_t dst_snap_id; + r = create_snap(m_dst_image_ctx, snap_name, &dst_snap_id); + if (r < 0) { + return r; + } + + // collection of all existing snaps in dst image + SnapIds dst_snap_ids({dst_snap_id}); + if (!m_snap_map.empty()) { + dst_snap_ids.insert(dst_snap_ids.end(), + m_snap_map.rbegin()->second.begin(), + m_snap_map.rbegin()->second.end()); + } + m_snap_map[src_snap_id] = dst_snap_ids; + m_snap_seqs[src_snap_id] = dst_snap_id; + return 0; + } +}; + +TEST_F(TestMockDeepCopyImageCopyRequest, SimpleImage) { + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + InSequence seq; + expect_get_image_size(mock_src_image_ctx, 1 << m_src_image_ctx->order); + expect_get_image_size(mock_src_image_ctx, 0); + expect_object_copy_send(mock_object_copy_request); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, snap_id_end, 0, false, boost::none, + m_snap_seqs, &no_op, &ctx); + request->send(); + + ASSERT_EQ(m_snap_map, wait_for_snap_map(mock_object_copy_request)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 0, nullptr, 0)); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, OutOfOrder) { + std::string max_ops_str; + ASSERT_EQ(0, _rados.conf_get("rbd_concurrent_management_ops", max_ops_str)); + ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", "10")); + BOOST_SCOPE_EXIT( (max_ops_str) ) { + ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", + max_ops_str.c_str())); + } BOOST_SCOPE_EXIT_END; + + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + uint64_t object_count = 55; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + expect_get_image_size(mock_src_image_ctx, + object_count * (1 << m_src_image_ctx->order)); + expect_get_image_size(mock_src_image_ctx, 0); + + EXPECT_CALL(mock_object_copy_request, send()).Times(object_count); + + class ProgressContext : public librbd::ProgressContext { + public: + uint64_t object_count; + librbd::deep_copy::ObjectNumber expected_object_number; + + ProgressContext(uint64_t object_count) + : object_count(object_count) { + } + + int update_progress(uint64_t object_no, uint64_t end_object_no) override { + EXPECT_LE(object_no, object_count); + EXPECT_EQ(end_object_no, object_count); + if (!expected_object_number) { + expected_object_number = 0; + } else { + expected_object_number = *expected_object_number + 1; + } + EXPECT_EQ(*expected_object_number, object_no - 1); + + return 0; + } + } prog_ctx(object_count); + + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, snap_id_end, 0, false, boost::none, + m_snap_seqs, &prog_ctx, &ctx); + request->send(); + + std::map<uint64_t, Context*> copy_contexts; + ASSERT_EQ(m_snap_map, wait_for_snap_map(mock_object_copy_request)); + for (uint64_t i = 0; i < object_count; ++i) { + if (i % 10 == 0) { + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, i, + ©_contexts[i], 0)); + } else { + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, i, nullptr, + 0)); + } + } + + for (auto& pair : copy_contexts) { + pair.second->complete(0); + } + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, SnapshotSubset) { + librados::snap_t snap_id_start; + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("snap1")); + ASSERT_EQ(0, create_snap("snap2", &snap_id_start)); + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + InSequence seq; + expect_get_image_size(mock_src_image_ctx, 1 << m_src_image_ctx->order); + expect_get_image_size(mock_src_image_ctx, 0); + expect_get_image_size(mock_src_image_ctx, 0); + expect_get_image_size(mock_src_image_ctx, 0); + expect_object_copy_send(mock_object_copy_request); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + snap_id_start, snap_id_end, 0, false, + boost::none, m_snap_seqs, &no_op, + &ctx); + request->send(); + + SnapMap snap_map(m_snap_map); + snap_map.erase(snap_map.begin()); + ASSERT_EQ(snap_map, wait_for_snap_map(mock_object_copy_request)); + + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 0, nullptr, 0)); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, RestartPartialSync) { + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + InSequence seq; + expect_get_image_size(mock_src_image_ctx, 2 * (1 << m_src_image_ctx->order)); + expect_get_image_size(mock_src_image_ctx, 0); + expect_object_copy_send(mock_object_copy_request); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, snap_id_end, 0, false, + librbd::deep_copy::ObjectNumber{0U}, + m_snap_seqs, &no_op, &ctx); + request->send(); + + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 1, nullptr, 0)); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, Cancel) { + std::string max_ops_str; + ASSERT_EQ(0, _rados.conf_get("rbd_concurrent_management_ops", max_ops_str)); + ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", "1")); + BOOST_SCOPE_EXIT( (max_ops_str) ) { + ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", + max_ops_str.c_str())); + } BOOST_SCOPE_EXIT_END; + + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + InSequence seq; + expect_get_image_size(mock_src_image_ctx, 1 << m_src_image_ctx->order); + expect_get_image_size(mock_src_image_ctx, 0); + expect_object_copy_send(mock_object_copy_request); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, snap_id_end, 0, false, boost::none, + m_snap_seqs, &no_op, &ctx); + request->send(); + + ASSERT_EQ(m_snap_map, wait_for_snap_map(mock_object_copy_request)); + request->cancel(); + + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 0, nullptr, 0)); + ASSERT_EQ(-ECANCELED, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, Cancel_Inflight_Sync) { + std::string max_ops_str; + ASSERT_EQ(0, _rados.conf_get("rbd_concurrent_management_ops", max_ops_str)); + ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", "3")); + BOOST_SCOPE_EXIT( (max_ops_str) ) { + ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", + max_ops_str.c_str())); + } BOOST_SCOPE_EXIT_END; + + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + InSequence seq; + expect_get_image_size(mock_src_image_ctx, 6 * (1 << m_src_image_ctx->order)); + expect_get_image_size(mock_src_image_ctx, m_image_size); + + EXPECT_CALL(mock_object_copy_request, send()).Times(6); + + struct ProgressContext : public librbd::ProgressContext { + librbd::deep_copy::ObjectNumber object_number; + + int update_progress(uint64_t object_no, uint64_t end_object_no) override { + object_number = object_number ? *object_number + 1 : 0; + return 0; + } + } prog_ctx; + + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, snap_id_end, 0, false, boost::none, + m_snap_seqs, &prog_ctx, &ctx); + request->send(); + + ASSERT_EQ(m_snap_map, wait_for_snap_map(mock_object_copy_request)); + + Context *cancel_ctx = nullptr; + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 0, nullptr, 0)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 1, nullptr, 0)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 2, nullptr, 0)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 3, &cancel_ctx, + 0)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 4, nullptr, 0)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 5, nullptr, 0)); + + request->cancel(); + cancel_ctx->complete(0); + + ASSERT_EQ(-ECANCELED, ctx.wait()); + ASSERT_EQ(5u, prog_ctx.object_number.get()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, MissingSnap) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, 123, 0, false, boost::none, + m_snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, MissingFromSnap) { + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 123, snap_id_end, 0, false, + boost::none, m_snap_seqs, &no_op, + &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, EmptySnapMap) { + librados::snap_t snap_id_start; + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("snap1", &snap_id_start)); + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + snap_id_start, snap_id_end, 0, false, + boost::none, {{0, 0}}, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, EmptySnapSeqs) { + librados::snap_t snap_id_start; + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("snap1", &snap_id_start)); + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + snap_id_start, snap_id_end, 0, false, + boost::none, {}, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace deep_copy +} // namespace librbd diff --git a/src/test/librbd/deep_copy/test_mock_MetadataCopyRequest.cc b/src/test/librbd/deep_copy/test_mock_MetadataCopyRequest.cc new file mode 100644 index 00000000..8717f684 --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_MetadataCopyRequest.cc @@ -0,0 +1,180 @@ +// -*- 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 "include/rbd/librbd.hpp" +#include "include/stringify.h" +#include "librbd/ImageCtx.h" +#include "librbd/deep_copy/MetadataCopyRequest.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/test_support.h" +#include <map> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/MetadataCopyRequest.cc" + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockDeepCopyMetadataCopyRequest : public TestMockFixture { +public: + typedef MetadataCopyRequest<librbd::MockTestImageCtx> MockMetadataCopyRequest; + typedef std::map<std::string, bufferlist> Metadata; + + librbd::ImageCtx *m_src_image_ctx; + librbd::ImageCtx *m_dst_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_src_image_ctx)); + + librbd::RBD rbd; + std::string dst_image_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(rbd, m_ioctx, dst_image_name, m_image_size)); + ASSERT_EQ(0, open_image(dst_image_name, &m_dst_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_src_image_ctx->cct, + &m_thread_pool, &m_work_queue); + } + + void expect_metadata_list(librbd::MockTestImageCtx &mock_image_ctx, + const Metadata& metadata, int r) { + bufferlist out_bl; + encode(metadata, out_bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("metadata_list"), _, _, _)) + .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(out_bl)), + Return(r))); + } + + void expect_metadata_set(librbd::MockTestImageCtx &mock_image_ctx, + const Metadata& metadata, int r) { + bufferlist in_bl; + encode(metadata, in_bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("metadata_set"), ContentsEqual(in_bl), _, _)) + .WillOnce(Return(r)); + } +}; + +TEST_F(TestMockDeepCopyMetadataCopyRequest, Success) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + size_t idx = 1; + Metadata key_values_1; + for (; idx <= 128; ++idx) { + bufferlist bl; + bl.append("value" + stringify(idx)); + key_values_1.emplace("key" + stringify(idx), bl); + } + + Metadata key_values_2; + for (; idx <= 255; ++idx) { + bufferlist bl; + bl.append("value" + stringify(idx)); + key_values_2.emplace("key" + stringify(idx), bl); + } + + InSequence seq; + expect_metadata_list(mock_src_image_ctx, key_values_1, 0); + expect_metadata_set(mock_dst_image_ctx, key_values_1, 0); + expect_metadata_list(mock_src_image_ctx, key_values_2, 0); + expect_metadata_set(mock_dst_image_ctx, key_values_2, 0); + + C_SaferCond ctx; + auto request = MockMetadataCopyRequest::create(&mock_src_image_ctx, + &mock_dst_image_ctx, + &ctx); + request->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyMetadataCopyRequest, Empty) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + Metadata key_values; + + InSequence seq; + expect_metadata_list(mock_src_image_ctx, key_values, 0); + + C_SaferCond ctx; + auto request = MockMetadataCopyRequest::create(&mock_src_image_ctx, + &mock_dst_image_ctx, + &ctx); + request->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyMetadataCopyRequest, MetadataListError) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + Metadata key_values; + + InSequence seq; + expect_metadata_list(mock_src_image_ctx, key_values, -EINVAL); + + C_SaferCond ctx; + auto request = MockMetadataCopyRequest::create(&mock_src_image_ctx, + &mock_dst_image_ctx, + &ctx); + request->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyMetadataCopyRequest, MetadataSetError) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + Metadata key_values; + bufferlist bl; + bl.append("value"); + key_values.emplace("key", bl); + + InSequence seq; + expect_metadata_list(mock_src_image_ctx, key_values, 0); + expect_metadata_set(mock_dst_image_ctx, key_values, -EINVAL); + + C_SaferCond ctx; + auto request = MockMetadataCopyRequest::create(&mock_src_image_ctx, + &mock_dst_image_ctx, + &ctx); + request->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace deep_sync +} // namespace librbd diff --git a/src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc b/src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc new file mode 100644 index 00000000..ebaf479b --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc @@ -0,0 +1,1040 @@ +// -*- 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 "include/interval_set.h" +#include "include/rbd/librbd.hpp" +#include "include/rbd/object_map_types.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/Operations.h" +#include "librbd/api/Image.h" +#include "librbd/deep_copy/ObjectCopyRequest.h" +#include "librbd/io/ImageRequest.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/io/ReadResult.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/test_support.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } + + MockTestImageCtx *parent = nullptr; +}; + +} // anonymous namespace + +namespace util { + +inline ImageCtx* get_image_ctx(MockTestImageCtx* image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util + +namespace io { + +template <> +struct ImageRequest<MockTestImageCtx> { + static ImageRequest *s_instance; + + static void aio_read(MockTestImageCtx *ictx, AioCompletion *c, + Extents &&image_extents, ReadResult &&read_result, + int op_flags, const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_read(c, image_extents); + } + MOCK_METHOD2(aio_read, void(AioCompletion *, const Extents&)); +}; + +ImageRequest<MockTestImageCtx> *ImageRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace io + +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/ObjectCopyRequest.cc" +template class librbd::deep_copy::ObjectCopyRequest<librbd::MockTestImageCtx>; + +static bool operator==(const SnapContext& rhs, const SnapContext& lhs) { + return (rhs.seq == lhs.seq && rhs.snaps == lhs.snaps); +} + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::ReturnNew; +using ::testing::WithArg; + +namespace { + +void scribble(librbd::ImageCtx *image_ctx, int num_ops, size_t max_size, + interval_set<uint64_t> *what) +{ + uint64_t object_size = 1 << image_ctx->order; + for (int i = 0; i < num_ops; i++) { + uint64_t off = rand() % (object_size - max_size + 1); + uint64_t len = 1 + rand() % max_size; + std::cout << __func__ << ": off=" << off << ", len=" << len << std::endl; + + bufferlist bl; + bl.append(std::string(len, '1')); + + int r = image_ctx->io_work_queue->write(off, len, std::move(bl), 0); + ASSERT_EQ(static_cast<int>(len), r); + + interval_set<uint64_t> w; + w.insert(off, len); + what->union_of(w); + } + std::cout << " wrote " << *what << std::endl; +} + +} // anonymous namespace + +class TestMockDeepCopyObjectCopyRequest : public TestMockFixture { +public: + typedef ObjectCopyRequest<librbd::MockTestImageCtx> MockObjectCopyRequest; + + librbd::ImageCtx *m_src_image_ctx; + librbd::ImageCtx *m_dst_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + SnapMap m_snap_map; + std::vector<librados::snap_t> m_src_snap_ids; + std::vector<librados::snap_t> m_dst_snap_ids; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_src_image_ctx)); + + librbd::NoOpProgressContext no_op; + m_image_size = 1 << m_src_image_ctx->order; + ASSERT_EQ(0, m_src_image_ctx->operations->resize(m_image_size, true, no_op)); + + librbd::RBD rbd; + std::string dst_image_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(rbd, m_ioctx, dst_image_name, m_image_size)); + ASSERT_EQ(0, open_image(dst_image_name, &m_dst_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_src_image_ctx->cct, + &m_thread_pool, &m_work_queue); + } + + bool is_fast_diff(librbd::MockImageCtx &mock_image_ctx) { + return (mock_image_ctx.features & RBD_FEATURE_FAST_DIFF) != 0; + } + + void prepare_exclusive_lock(librbd::MockImageCtx &mock_image_ctx, + librbd::MockExclusiveLock &mock_exclusive_lock) { + if ((mock_image_ctx.features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) { + return; + } + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + void expect_get_object_count(librbd::MockImageCtx& mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, get_object_count(_)) + .WillRepeatedly(Invoke([&mock_image_ctx](librados::snap_t snap_id) { + return mock_image_ctx.image_ctx->get_object_count(snap_id); + })); + } + + void expect_test_features(librbd::MockImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_)) + .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) { + return (mock_image_ctx.features & features) != 0; + }))); + } + + void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) { + if ((m_src_image_ctx->features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) { + return; + } + EXPECT_CALL(mock_exclusive_lock, start_op(_)).WillOnce( + ReturnNew<FunctionContext>([](int) {})); + } + + void expect_list_snaps(librbd::MockTestImageCtx &mock_image_ctx, + librados::MockTestMemIoCtxImpl &mock_io_ctx, + const librados::snap_set_t &snap_set) { + expect_get_object_name(mock_image_ctx); + expect_set_snap_read(mock_io_ctx, CEPH_SNAPDIR); + EXPECT_CALL(mock_io_ctx, + list_snaps(mock_image_ctx.image_ctx->get_object_name(0), _)) + .WillOnce(DoAll(WithArg<1>(Invoke([&snap_set](librados::snap_set_t *out_snap_set) { + *out_snap_set = snap_set; + })), + Return(0))); + } + + void expect_list_snaps(librbd::MockTestImageCtx &mock_image_ctx, + librados::MockTestMemIoCtxImpl &mock_io_ctx, int r) { + expect_get_object_name(mock_image_ctx); + expect_set_snap_read(mock_io_ctx, CEPH_SNAPDIR); + auto &expect = EXPECT_CALL(mock_io_ctx, + list_snaps(mock_image_ctx.image_ctx->get_object_name(0), + _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_object_name(librbd::MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, get_object_name(0)) + .WillOnce(Return(mock_image_ctx.image_ctx->get_object_name(0))); + } + + MockObjectCopyRequest *create_request( + librbd::MockTestImageCtx &mock_src_image_ctx, + librbd::MockTestImageCtx &mock_dst_image_ctx, + librados::snap_t src_snap_id_start, + librados::snap_t dst_snap_id_start, + Context *on_finish) { + expect_get_object_name(mock_dst_image_ctx); + return new MockObjectCopyRequest(&mock_src_image_ctx, &mock_dst_image_ctx, + src_snap_id_start, dst_snap_id_start, + m_snap_map, 0, false, on_finish); + } + + void expect_set_snap_read(librados::MockTestMemIoCtxImpl &mock_io_ctx, + uint64_t snap_id) { + EXPECT_CALL(mock_io_ctx, set_snap_read(snap_id)); + } + + void expect_sparse_read(librados::MockTestMemIoCtxImpl &mock_io_ctx, uint64_t offset, + uint64_t length, int r) { + + auto &expect = EXPECT_CALL(mock_io_ctx, sparse_read(_, offset, length, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_sparse_read(librados::MockTestMemIoCtxImpl &mock_io_ctx, + const interval_set<uint64_t> &extents, int r) { + for (auto extent : extents) { + expect_sparse_read(mock_io_ctx, extent.first, extent.second, r); + if (r < 0) { + break; + } + } + } + + void expect_write(librados::MockTestMemIoCtxImpl &mock_io_ctx, + uint64_t offset, uint64_t length, + const SnapContext &snapc, int r) { + auto &expect = EXPECT_CALL(mock_io_ctx, write(_, _, length, offset, snapc)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_write(librados::MockTestMemIoCtxImpl &mock_io_ctx, + const interval_set<uint64_t> &extents, + const SnapContext &snapc, int r) { + for (auto extent : extents) { + expect_write(mock_io_ctx, extent.first, extent.second, snapc, r); + if (r < 0) { + break; + } + } + } + + void expect_truncate(librados::MockTestMemIoCtxImpl &mock_io_ctx, + uint64_t offset, int r) { + auto &expect = EXPECT_CALL(mock_io_ctx, truncate(_, offset, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_remove(librados::MockTestMemIoCtxImpl &mock_io_ctx, int r) { + auto &expect = EXPECT_CALL(mock_io_ctx, remove(_, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_update_object_map(librbd::MockTestImageCtx &mock_image_ctx, + librbd::MockObjectMap &mock_object_map, + librados::snap_t snap_id, uint8_t state, + int r) { + if (mock_image_ctx.image_ctx->object_map != nullptr) { + auto &expect = EXPECT_CALL(mock_object_map, aio_update(snap_id, 0, 1, state, _, _, false, _)); + if (r < 0) { + expect.WillOnce(DoAll(WithArg<7>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + })), + Return(true))); + } else { + expect.WillOnce(DoAll(WithArg<7>(Invoke([&mock_image_ctx, snap_id, state](Context *ctx) { + ceph_assert(mock_image_ctx.image_ctx->snap_lock.is_locked()); + ceph_assert(mock_image_ctx.image_ctx->object_map_lock.is_wlocked()); + mock_image_ctx.image_ctx->object_map->aio_update<Context>( + snap_id, 0, 1, state, boost::none, {}, false, ctx); + })), + Return(true))); + } + } + } + + int create_snap(librbd::ImageCtx *image_ctx, const char* snap_name, + librados::snap_t *snap_id) { + int r = image_ctx->operations->snap_create( + cls::rbd::UserSnapshotNamespace(), snap_name); + if (r < 0) { + return r; + } + + r = image_ctx->state->refresh(); + if (r < 0) { + return r; + } + + if (image_ctx->snap_ids.count({cls::rbd::UserSnapshotNamespace(), + snap_name}) == 0) { + return -ENOENT; + } + + if (snap_id != nullptr) { + *snap_id = image_ctx->snap_ids[{cls::rbd::UserSnapshotNamespace(), + snap_name}]; + } + return 0; + } + + int create_snap(const char* snap_name) { + librados::snap_t src_snap_id; + int r = create_snap(m_src_image_ctx, snap_name, &src_snap_id); + if (r < 0) { + return r; + } + + librados::snap_t dst_snap_id; + r = create_snap(m_dst_image_ctx, snap_name, &dst_snap_id); + if (r < 0) { + return r; + } + + // collection of all existing snaps in dst image + SnapIds dst_snap_ids({dst_snap_id}); + if (!m_snap_map.empty()) { + dst_snap_ids.insert(dst_snap_ids.end(), + m_snap_map.rbegin()->second.begin(), + m_snap_map.rbegin()->second.end()); + } + m_snap_map[src_snap_id] = dst_snap_ids; + m_src_snap_ids.push_back(src_snap_id); + m_dst_snap_ids.push_back(dst_snap_id); + + return 0; + } + + std::string get_snap_name(librbd::ImageCtx *image_ctx, + librados::snap_t snap_id) { + auto it = std::find_if(image_ctx->snap_ids.begin(), + image_ctx->snap_ids.end(), + [snap_id](const std::pair<std::pair<cls::rbd::SnapshotNamespace, + std::string>, + librados::snap_t> &pair) { + return (pair.second == snap_id); + }); + if (it == image_ctx->snap_ids.end()) { + return ""; + } + return it->first.second; + } + + int copy_objects() { + int r; + uint64_t object_size = 1 << m_src_image_ctx->order; + + bufferlist bl; + bl.append(std::string(object_size, '1')); + r = m_src_image_ctx->io_work_queue->read( + 0, object_size, librbd::io::ReadResult{&bl}, 0); + if (r < 0) { + return r; + } + + r = m_dst_image_ctx->io_work_queue->write(0, object_size, std::move(bl), 0); + if (r < 0) { + return r; + } + + return 0; + } + + int compare_objects() { + SnapMap snap_map(m_snap_map); + if (snap_map.empty()) { + return -ENOENT; + } + + int r; + uint64_t object_size = 1 << m_src_image_ctx->order; + while (!snap_map.empty()) { + librados::snap_t src_snap_id = snap_map.begin()->first; + librados::snap_t dst_snap_id = *snap_map.begin()->second.begin(); + snap_map.erase(snap_map.begin()); + + std::string snap_name = get_snap_name(m_src_image_ctx, src_snap_id); + if (snap_name.empty()) { + return -ENOENT; + } + + std::cout << "comparing '" << snap_name << " (" << src_snap_id + << " to " << dst_snap_id << ")" << std::endl; + + r = librbd::api::Image<>::snap_set(m_src_image_ctx, + cls::rbd::UserSnapshotNamespace(), + snap_name.c_str()); + if (r < 0) { + return r; + } + + r = librbd::api::Image<>::snap_set(m_dst_image_ctx, + cls::rbd::UserSnapshotNamespace(), + snap_name.c_str()); + if (r < 0) { + return r; + } + + bufferlist src_bl; + src_bl.append(std::string(object_size, '1')); + r = m_src_image_ctx->io_work_queue->read( + 0, object_size, librbd::io::ReadResult{&src_bl}, 0); + if (r < 0) { + return r; + } + + bufferlist dst_bl; + dst_bl.append(std::string(object_size, '1')); + r = m_dst_image_ctx->io_work_queue->read( + 0, object_size, librbd::io::ReadResult{&dst_bl}, 0); + if (r < 0) { + return r; + } + + if (!src_bl.contents_equal(dst_bl)) { + std::cout << "src block: " << std::endl; src_bl.hexdump(std::cout); + std::cout << "dst block: " << std::endl; dst_bl.hexdump(std::cout); + return -EBADMSG; + } + } + + r = librbd::api::Image<>::snap_set(m_src_image_ctx, + cls::rbd::UserSnapshotNamespace(), + nullptr); + if (r < 0) { + return r; + } + r = librbd::api::Image<>::snap_set(m_dst_image_ctx, + cls::rbd::UserSnapshotNamespace(), + nullptr); + if (r < 0) { + return r; + } + + return 0; + } +}; + +TEST_F(TestMockDeepCopyObjectCopyRequest, DNE) { + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + expect_test_features(mock_dst_image_ctx); + expect_get_object_count(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, 0, + &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, -ENOENT); + + request->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, Write) { + // scribble some data + interval_set<uint64_t> one; + scribble(m_src_image_ctx, 10, 102400, &one); + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + expect_get_object_count(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, 0, + &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[0], OBJECT_EXISTS, 0); + + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, compare_objects()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, ReadMissingStaleSnapSet) { + ASSERT_EQ(0, create_snap("one")); + ASSERT_EQ(0, create_snap("two")); + + // scribble some data + interval_set<uint64_t> one; + scribble(m_src_image_ctx, 10, 102400, &one); + ASSERT_EQ(0, create_snap("three")); + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + expect_get_object_count(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, 0, + &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + librados::clone_info_t dummy_clone_info; + dummy_clone_info.cloneid = librados::SNAP_HEAD; + dummy_clone_info.size = 123; + + librados::snap_set_t dummy_snap_set1; + dummy_snap_set1.clones.push_back(dummy_clone_info); + + dummy_clone_info.size = 234; + librados::snap_set_t dummy_snap_set2; + dummy_snap_set2.clones.push_back(dummy_clone_info); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, dummy_snap_set1); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[3]); + expect_sparse_read(mock_src_io_ctx, 0, 123, -ENOENT); + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, dummy_snap_set2); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[3]); + expect_sparse_read(mock_src_io_ctx, 0, 234, -ENOENT); + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[3]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), + {m_dst_snap_ids[1], {m_dst_snap_ids[1], + m_dst_snap_ids[0]}}, + 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[2], OBJECT_EXISTS, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[3], is_fast_diff(mock_dst_image_ctx) ? + OBJECT_EXISTS_CLEAN : OBJECT_EXISTS, 0); + + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, compare_objects()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, ReadMissingUpToDateSnapMap) { + // scribble some data + interval_set<uint64_t> one; + scribble(m_src_image_ctx, 10, 102400, &one); + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, 0, + &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), -ENOENT); + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + + request->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, ReadError) { + // scribble some data + interval_set<uint64_t> one; + scribble(m_src_image_ctx, 10, 102400, &one); + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, 0, + &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), -EINVAL); + + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, WriteError) { + // scribble some data + interval_set<uint64_t> one; + scribble(m_src_image_ctx, 10, 102400, &one); + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + expect_get_object_count(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, 0, + &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, -EINVAL); + + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, WriteSnaps) { + // scribble some data + interval_set<uint64_t> one; + scribble(m_src_image_ctx, 10, 102400, &one); + ASSERT_EQ(0, create_snap("one")); + + interval_set<uint64_t> two; + scribble(m_src_image_ctx, 10, 102400, &two); + ASSERT_EQ(0, create_snap("two")); + + if (one.range_end() < two.range_end()) { + interval_set<uint64_t> resize_diff; + resize_diff.insert(one.range_end(), two.range_end() - one.range_end()); + two.union_of(resize_diff); + } + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + expect_get_object_count(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, 0, + &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[2]); + expect_sparse_read(mock_src_io_ctx, two, 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, two, + {m_dst_snap_ids[0], {m_dst_snap_ids[0]}}, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[0], OBJECT_EXISTS, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[1], OBJECT_EXISTS, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[2], is_fast_diff(mock_dst_image_ctx) ? + OBJECT_EXISTS_CLEAN : OBJECT_EXISTS, 0); + + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, compare_objects()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, Trim) { + ASSERT_EQ(0, m_src_image_ctx->operations->metadata_set( + "conf_rbd_skip_partial_discard", "false")); + m_src_image_ctx->discard_granularity_bytes = 0; + + // scribble some data + interval_set<uint64_t> one; + scribble(m_src_image_ctx, 10, 102400, &one); + ASSERT_EQ(0, create_snap("one")); + + // trim the object + uint64_t trim_offset = rand() % one.range_end(); + ASSERT_LE(0, m_src_image_ctx->io_work_queue->discard( + trim_offset, one.range_end() - trim_offset, + m_src_image_ctx->discard_granularity_bytes)); + ASSERT_EQ(0, create_snap("copy")); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + expect_get_object_count(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, 0, + &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, 0); + expect_start_op(mock_exclusive_lock); + expect_truncate(mock_dst_io_ctx, trim_offset, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[0], OBJECT_EXISTS, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[1], OBJECT_EXISTS, 0); + + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, compare_objects()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, Remove) { + // scribble some data + interval_set<uint64_t> one; + scribble(m_src_image_ctx, 10, 102400, &one); + ASSERT_EQ(0, create_snap("one")); + ASSERT_EQ(0, create_snap("two")); + + // remove the object + uint64_t object_size = 1 << m_src_image_ctx->order; + ASSERT_LE(0, m_src_image_ctx->io_work_queue->discard( + 0, object_size, m_src_image_ctx->discard_granularity_bytes)); + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + expect_get_object_count(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, 0, + &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[1]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, 0); + expect_start_op(mock_exclusive_lock); + expect_remove(mock_dst_io_ctx, 0); + expect_start_op(mock_exclusive_lock); + uint8_t state = OBJECT_EXISTS; + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[0], state, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[1], is_fast_diff(mock_dst_image_ctx) ? + OBJECT_EXISTS_CLEAN : OBJECT_EXISTS, 0); + + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, compare_objects()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, ObjectMapUpdateError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + // scribble some data + interval_set<uint64_t> one; + scribble(m_src_image_ctx, 10, 102400, &one); + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + expect_get_object_count(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, 0, + &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[0], OBJECT_EXISTS, -EBLACKLISTED); + + request->send(); + ASSERT_EQ(-EBLACKLISTED, ctx.wait()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, WriteSnapsStart) { + // scribble some data + interval_set<uint64_t> one; + scribble(m_src_image_ctx, 10, 102400, &one); + ASSERT_EQ(0, copy_objects()); + ASSERT_EQ(0, create_snap("one")); + + auto src_snap_id_start = m_src_image_ctx->snaps[0]; + auto dst_snap_id_start = m_dst_image_ctx->snaps[0]; + + interval_set<uint64_t> two; + scribble(m_src_image_ctx, 10, 102400, &two); + ASSERT_EQ(0, create_snap("two")); + + interval_set<uint64_t> three; + scribble(m_src_image_ctx, 10, 102400, &three); + ASSERT_EQ(0, create_snap("three")); + + auto max_extent = one.range_end(); + if (max_extent < two.range_end()) { + interval_set<uint64_t> resize_diff; + resize_diff.insert(max_extent, two.range_end() - max_extent); + two.union_of(resize_diff); + } + + max_extent = std::max(max_extent, two.range_end()); + if (max_extent < three.range_end()) { + interval_set<uint64_t> resize_diff; + resize_diff.insert(max_extent, three.range_end() - max_extent); + three.union_of(resize_diff); + } + + interval_set<uint64_t> four; + scribble(m_src_image_ctx, 10, 102400, &four); + + // map should begin after src start and src end's dst snap seqs should + // point to HEAD revision + m_snap_map.erase(src_snap_id_start); + m_snap_map[m_src_image_ctx->snaps[0]][0] = CEPH_NOSNAP; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + expect_get_object_count(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, + src_snap_id_start, + dst_snap_id_start, + &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[1]); + expect_sparse_read(mock_src_io_ctx, two, 0); + + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[2]); + expect_sparse_read(mock_src_io_ctx, three, 0); + + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, two, + {m_dst_snap_ids[0], {m_dst_snap_ids[0]}}, 0); + + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, three, + {m_dst_snap_ids[1], {m_dst_snap_ids[1], m_dst_snap_ids[0]}}, 0); + + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[1], OBJECT_EXISTS, 0); + + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + CEPH_NOSNAP, OBJECT_EXISTS, 0); + + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, compare_objects()); +} + +} // namespace deep_copy +} // namespace librbd diff --git a/src/test/librbd/deep_copy/test_mock_SetHeadRequest.cc b/src/test/librbd/deep_copy/test_mock_SetHeadRequest.cc new file mode 100644 index 00000000..48c2f3b0 --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_SetHeadRequest.cc @@ -0,0 +1,293 @@ +// -*- 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/librados_test_stub/LibradosTestStub.h" +#include "include/rbd/librbd.hpp" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "osdc/Striper.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "librbd/deep_copy/SetHeadRequest.h" +#include "librbd/image/AttachParentRequest.h" +#include "librbd/image/DetachParentRequest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace image { + +template <> +struct AttachParentRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static AttachParentRequest* s_instance; + static AttachParentRequest* create(MockTestImageCtx&, + const cls::rbd::ParentImageSpec& pspec, + uint64_t parent_overlap, bool reattach, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + AttachParentRequest() { + s_instance = this; + } +}; + +AttachParentRequest<MockTestImageCtx>* AttachParentRequest<MockTestImageCtx>::s_instance = nullptr; + +template <> +class DetachParentRequest<MockTestImageCtx> { +public: + static DetachParentRequest *s_instance; + static DetachParentRequest *create(MockTestImageCtx &image_ctx, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + DetachParentRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +DetachParentRequest<MockTestImageCtx> *DetachParentRequest<MockTestImageCtx>::s_instance; + +} // namespace image +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/SetHeadRequest.cc" +template class librbd::deep_copy::SetHeadRequest<librbd::MockTestImageCtx>; + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; +using ::testing::ReturnNew; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockDeepCopySetHeadRequest : public TestMockFixture { +public: + typedef SetHeadRequest<librbd::MockTestImageCtx> MockSetHeadRequest; + typedef image::AttachParentRequest<MockTestImageCtx> MockAttachParentRequest; + typedef image::DetachParentRequest<MockTestImageCtx> MockDetachParentRequest; + + librbd::ImageCtx *m_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_image_ctx->cct, &m_thread_pool, + &m_work_queue); + } + + void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) { + EXPECT_CALL(mock_exclusive_lock, start_op(_)).WillOnce( + ReturnNew<FunctionContext>([](int) {})); + } + + void expect_test_features(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t features, bool enabled) { + EXPECT_CALL(mock_image_ctx, test_features(features)) + .WillOnce(Return(enabled)); + } + + void expect_set_size(librbd::MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("set_size"), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_detach_parent(MockImageCtx &mock_image_ctx, + MockDetachParentRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(FinishRequest(&mock_request, r, &mock_image_ctx)); + } + + void expect_attach_parent(MockImageCtx &mock_image_ctx, + MockAttachParentRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(FinishRequest(&mock_request, r, &mock_image_ctx)); + } + + MockSetHeadRequest *create_request( + librbd::MockTestImageCtx &mock_local_image_ctx, uint64_t size, + const cls::rbd::ParentImageSpec &parent_spec, uint64_t parent_overlap, + Context *on_finish) { + return new MockSetHeadRequest(&mock_local_image_ctx, size, parent_spec, + parent_overlap, on_finish); + } +}; + +TEST_F(TestMockDeepCopySetHeadRequest, Resize) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + expect_set_size(mock_image_ctx, 0); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, 123, {}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, ResizeError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + expect_set_size(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, 123, {}, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, RemoveParent) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + mock_image_ctx.parent_md.spec.pool_id = 213; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + MockDetachParentRequest mock_detach_parent; + expect_detach_parent(mock_image_ctx, mock_detach_parent, 0); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, m_image_ctx->size, {}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, RemoveParentError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + mock_image_ctx.parent_md.spec.pool_id = 213; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + MockDetachParentRequest mock_detach_parent; + expect_detach_parent(mock_image_ctx, mock_detach_parent, -EINVAL); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, m_image_ctx->size, {}, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, RemoveSetParent) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + mock_image_ctx.parent_md.spec.pool_id = 213; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + MockDetachParentRequest mock_detach_parent; + expect_detach_parent(mock_image_ctx, mock_detach_parent, 0); + expect_start_op(mock_exclusive_lock); + MockAttachParentRequest mock_attach_parent; + expect_attach_parent(mock_image_ctx, mock_attach_parent, 0); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, m_image_ctx->size, + {123, "", "test", 0}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, SetParentSpec) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + MockAttachParentRequest mock_attach_parent; + expect_attach_parent(mock_image_ctx, mock_attach_parent, 0); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, m_image_ctx->size, + {123, "", "test", 0}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, SetParentOverlap) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + mock_image_ctx.parent_md.spec = {123, "", "test", 0}; + mock_image_ctx.parent_md.overlap = m_image_ctx->size; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + expect_set_size(mock_image_ctx, 0); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, 123, + mock_image_ctx.parent_md.spec, 123, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(123U, mock_image_ctx.parent_md.overlap); +} + +TEST_F(TestMockDeepCopySetHeadRequest, SetParentError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + MockAttachParentRequest mock_attach_parent; + expect_attach_parent(mock_image_ctx, mock_attach_parent, -ESTALE); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, m_image_ctx->size, + {123, "", "test", 0}, 0, &ctx); + request->send(); + ASSERT_EQ(-ESTALE, ctx.wait()); +} + +} // namespace deep_copy +} // namespace librbd diff --git a/src/test/librbd/deep_copy/test_mock_SnapshotCopyRequest.cc b/src/test/librbd/deep_copy/test_mock_SnapshotCopyRequest.cc new file mode 100644 index 00000000..677d612b --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_SnapshotCopyRequest.cc @@ -0,0 +1,917 @@ +// -*- 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 "include/rbd/librbd.hpp" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/deep_copy/SetHeadRequest.h" +#include "librbd/deep_copy/SnapshotCopyRequest.h" +#include "librbd/deep_copy/SnapshotCreateRequest.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/test_support.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace deep_copy { + +template <> +class SetHeadRequest<librbd::MockTestImageCtx> { +public: + static SetHeadRequest* s_instance; + Context *on_finish; + + static SetHeadRequest* create(librbd::MockTestImageCtx *image_ctx, + uint64_t size, + const cls::rbd::ParentImageSpec &parent_spec, + uint64_t parent_overlap, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SetHeadRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template <> +struct SnapshotCreateRequest<librbd::MockTestImageCtx> { + static SnapshotCreateRequest* s_instance; + static SnapshotCreateRequest* create(librbd::MockTestImageCtx* image_ctx, + const std::string &snap_name, + const cls::rbd::SnapshotNamespace &snap_namespace, + uint64_t size, + const cls::rbd::ParentImageSpec &parent_spec, + uint64_t parent_overlap, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + SnapshotCreateRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +SetHeadRequest<librbd::MockTestImageCtx>* SetHeadRequest<librbd::MockTestImageCtx>::s_instance = nullptr; +SnapshotCreateRequest<librbd::MockTestImageCtx>* SnapshotCreateRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace deep_copy +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/SnapshotCopyRequest.cc" +template class librbd::deep_copy::SnapshotCopyRequest<librbd::MockTestImageCtx>; + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; +using ::testing::ReturnNew; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockDeepCopySnapshotCopyRequest : public TestMockFixture { +public: + typedef SetHeadRequest<librbd::MockTestImageCtx> MockSetHeadRequest; + typedef SnapshotCopyRequest<librbd::MockTestImageCtx> MockSnapshotCopyRequest; + typedef SnapshotCreateRequest<librbd::MockTestImageCtx> MockSnapshotCreateRequest; + + librbd::ImageCtx *m_src_image_ctx; + librbd::ImageCtx *m_dst_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + librbd::SnapSeqs m_snap_seqs; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_src_image_ctx)); + + librbd::RBD rbd; + std::string dst_image_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(rbd, m_ioctx, dst_image_name, m_image_size)); + ASSERT_EQ(0, open_image(dst_image_name, &m_dst_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_src_image_ctx->cct, + &m_thread_pool, &m_work_queue); + } + + void prepare_exclusive_lock(librbd::MockImageCtx &mock_image_ctx, + librbd::MockExclusiveLock &mock_exclusive_lock) { + if ((mock_image_ctx.features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) { + return; + } + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + void expect_test_features(librbd::MockImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_, _)) + .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) { + return (mock_image_ctx.features & features) != 0; + }))); + EXPECT_CALL(mock_image_ctx, test_features(_)) + .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) { + return (mock_image_ctx.features & features) != 0; + }))); + } + + void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) { + if ((m_src_image_ctx->features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) { + return; + } + EXPECT_CALL(mock_exclusive_lock, start_op(_)).WillOnce( + ReturnNew<FunctionContext>([](int) {})); + } + + void expect_get_snap_namespace(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id) { + EXPECT_CALL(mock_image_ctx, get_snap_namespace(snap_id, _)) + .WillOnce(Invoke([&mock_image_ctx](uint64_t snap_id, + cls::rbd::SnapshotNamespace* snap_ns) { + auto it = mock_image_ctx.snap_info.find(snap_id); + *snap_ns = it->second.snap_namespace; + return 0; + })); + } + + void expect_snap_create(librbd::MockTestImageCtx &mock_image_ctx, + MockSnapshotCreateRequest &mock_snapshot_create_request, + const std::string &snap_name, uint64_t snap_id, int r) { + EXPECT_CALL(mock_snapshot_create_request, send()) + .WillOnce(DoAll(Invoke([&mock_image_ctx, snap_id, snap_name]() { + inject_snap(mock_image_ctx, snap_id, snap_name); + }), + Invoke([this, &mock_snapshot_create_request, r]() { + m_work_queue->queue(mock_snapshot_create_request.on_finish, r); + }))); + } + + void expect_snap_remove(librbd::MockTestImageCtx &mock_image_ctx, + const std::string &snap_name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_remove(_, StrEq(snap_name), _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + }))); + } + + void expect_snap_protect(librbd::MockTestImageCtx &mock_image_ctx, + const std::string &snap_name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_protect(_, StrEq(snap_name), _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + }))); + } + + void expect_snap_unprotect(librbd::MockTestImageCtx &mock_image_ctx, + const std::string &snap_name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_unprotect(_, StrEq(snap_name), _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + }))); + } + + void expect_snap_is_protected(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, bool is_protected, int r) { + EXPECT_CALL(mock_image_ctx, is_snap_protected(snap_id, _)) + .WillOnce(DoAll(SetArgPointee<1>(is_protected), + Return(r))); + } + + void expect_snap_is_unprotected(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, bool is_unprotected, int r) { + EXPECT_CALL(mock_image_ctx, is_snap_unprotected(snap_id, _)) + .WillOnce(DoAll(SetArgPointee<1>(is_unprotected), + Return(r))); + } + + void expect_set_head(MockSetHeadRequest &mock_set_head_request, int r) { + EXPECT_CALL(mock_set_head_request, send()) + .WillOnce(Invoke([&mock_set_head_request, r]() { + mock_set_head_request.on_finish->complete(r); + })); + } + + static void inject_snap(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, const std::string &snap_name) { + mock_image_ctx.snap_ids[{cls::rbd::UserSnapshotNamespace(), + snap_name}] = snap_id; + } + + MockSnapshotCopyRequest *create_request( + librbd::MockTestImageCtx &mock_src_image_ctx, + librbd::MockTestImageCtx &mock_dst_image_ctx, + librados::snap_t src_snap_id_start, + librados::snap_t src_snap_id_end, + librados::snap_t dst_snap_id_start, + Context *on_finish) { + return new MockSnapshotCopyRequest(&mock_src_image_ctx, &mock_dst_image_ctx, + src_snap_id_start, src_snap_id_end, + dst_snap_id_start, false, m_work_queue, + &m_snap_seqs, on_finish); + } + + int create_snap(librbd::ImageCtx *image_ctx, + const cls::rbd::SnapshotNamespace& snap_ns, + const std::string &snap_name, bool protect) { + int r = image_ctx->operations->snap_create(snap_ns, snap_name.c_str()); + if (r < 0) { + return r; + } + + if (protect) { + EXPECT_TRUE(boost::get<cls::rbd::UserSnapshotNamespace>(&snap_ns) != + nullptr); + r = image_ctx->operations->snap_protect(snap_ns, snap_name.c_str()); + if (r < 0) { + return r; + } + } + + r = image_ctx->state->refresh(); + if (r < 0) { + return r; + } + return 0; + } + + int create_snap(librbd::ImageCtx *image_ctx, const std::string &snap_name, + bool protect = false) { + return create_snap(image_ctx, cls::rbd::UserSnapshotNamespace{}, snap_name, + protect); + } + + void validate_snap_seqs(const librbd::SnapSeqs &snap_seqs) { + ASSERT_EQ(snap_seqs, m_snap_seqs); + } +}; + +TEST_F(TestMockDeepCopySnapshotCopyRequest, Empty) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSetHeadRequest mock_set_head_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_set_head(mock_set_head_request, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapCreate) { + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1")); + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap2")); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t src_snap_id2 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap2"}]; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + MockSetHeadRequest mock_set_head_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap1", 12, 0); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id2); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap2", 14, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, false, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id2, false, 0); + expect_set_head(mock_set_head_request, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, 12}, {src_snap_id2, 14}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapCreateError) { + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1")); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + uint64_t src_snap_id1 = mock_src_image_ctx.snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap1", + 12, -EINVAL); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapCreateCancel) { + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1")); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_start_op(mock_exclusive_lock); + EXPECT_CALL(mock_snapshot_create_request, send()) + .WillOnce(DoAll(InvokeWithoutArgs([request]() { + request->cancel(); + }), + Invoke([this, &mock_snapshot_create_request]() { + m_work_queue->queue(mock_snapshot_create_request.on_finish, 0); + }))); + + request->send(); + ASSERT_EQ(-ECANCELED, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapRemoveAndCreate) { + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1")); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1")); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + MockSetHeadRequest mock_set_head_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, + m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}], + true, 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_remove(mock_dst_image_ctx, "snap1", 0); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap1", 12, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, false, 0); + expect_set_head(mock_set_head_request, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, 12}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapRemoveError) { + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1")); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, + m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}], + true, 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_remove(mock_dst_image_ctx, "snap1", -EINVAL); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapUnprotect) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSetHeadRequest mock_set_head_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_snap_is_unprotected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_unprotect(mock_dst_image_ctx, "snap1", 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, false, 0); + expect_set_head(mock_set_head_request, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, dst_snap_id1}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapUnprotectError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_snap_is_unprotected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_unprotect(mock_dst_image_ctx, "snap1", -EBUSY); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapUnprotectCancel) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_snap_is_unprotected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_start_op(mock_exclusive_lock); + EXPECT_CALL(*mock_dst_image_ctx.operations, + execute_snap_unprotect(_, StrEq("snap1"), _)) + .WillOnce(DoAll(InvokeWithoutArgs([request]() { + request->cancel(); + }), + WithArg<2>(Invoke([this](Context *ctx) { + m_work_queue->queue(ctx, 0); + })))); + + request->send(); + ASSERT_EQ(-ECANCELED, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapUnprotectRemove) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + MockSetHeadRequest mock_set_head_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, + m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}], + false, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_unprotect(mock_dst_image_ctx, "snap1", 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_remove(mock_dst_image_ctx, "snap1", 0); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap1", + 12, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, false, 0); + expect_set_head(mock_set_head_request, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, 12}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapCreateProtect) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + MockSetHeadRequest mock_set_head_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap1", + 12, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_snap_is_protected(mock_dst_image_ctx, 12, false, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_protect(mock_dst_image_ctx, "snap1", 0); + expect_set_head(mock_set_head_request, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, 12}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapProtect) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSetHeadRequest mock_set_head_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, true, 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_snap_is_protected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_protect(mock_dst_image_ctx, "snap1", 0); + expect_set_head(mock_set_head_request, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, dst_snap_id1}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapProtectError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, true, 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_snap_is_protected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_protect(mock_dst_image_ctx, "snap1", -EINVAL); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapProtectCancel) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, true, 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_snap_is_protected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_start_op(mock_exclusive_lock); + EXPECT_CALL(*mock_dst_image_ctx.operations, + execute_snap_protect(_, StrEq("snap1"), _)) + .WillOnce(DoAll(InvokeWithoutArgs([request]() { + request->cancel(); + }), + WithArg<2>(Invoke([this](Context *ctx) { + m_work_queue->queue(ctx, 0); + })))); + + request->send(); + ASSERT_EQ(-ECANCELED, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SetHeadError) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSetHeadRequest mock_set_head_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_set_head(mock_set_head_request, -EINVAL); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, 0, + CEPH_NOSNAP, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, NoSetHead) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap1", + 12, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, false, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx,0, + src_snap_id1, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, 12}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, StartEndLimit) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", false)); + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap2", false)); + ASSERT_EQ(0, create_snap(m_src_image_ctx, + {cls::rbd::TrashSnapshotNamespace{}}, + "snap3", false)); + auto src_snap_id1 = m_src_image_ctx->snaps[2]; + auto src_snap_id2 = m_src_image_ctx->snaps[1]; + auto src_snap_id3 = m_src_image_ctx->snaps[0]; + + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap0", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", false)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap3", false)); + auto dst_snap_id1 = m_dst_image_ctx->snaps[1]; + auto dst_snap_id3 = m_dst_image_ctx->snaps[0]; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id3, + true, 0); + + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id3); + expect_start_op(mock_exclusive_lock); + expect_snap_remove(mock_dst_image_ctx, "snap3", 0); + + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id2); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap2", + 12, 0); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id3); + + expect_snap_is_protected(mock_src_image_ctx, src_snap_id2, false, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id3, false, 0); + + MockSetHeadRequest mock_set_head_request; + expect_set_head(mock_set_head_request, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, + src_snap_id1, + src_snap_id3, + dst_snap_id1, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id2, 12}, {src_snap_id3, CEPH_NOSNAP}}); +} + +} // namespace deep_copy +} // namespace librbd diff --git a/src/test/librbd/deep_copy/test_mock_SnapshotCreateRequest.cc b/src/test/librbd/deep_copy/test_mock_SnapshotCreateRequest.cc new file mode 100644 index 00000000..57945e70 --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_SnapshotCreateRequest.cc @@ -0,0 +1,256 @@ +// -*- 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/librados_test_stub/LibradosTestStub.h" +#include "include/rbd/librbd.hpp" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "osdc/Striper.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "librbd/deep_copy/SetHeadRequest.h" +#include "librbd/deep_copy/SnapshotCreateRequest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace deep_copy { + +template <> +class SetHeadRequest<librbd::MockTestImageCtx> { +public: + static SetHeadRequest* s_instance; + Context *on_finish; + + static SetHeadRequest* create(librbd::MockTestImageCtx *image_ctx, + uint64_t size, + const cls::rbd::ParentImageSpec &parent_spec, + uint64_t parent_overlap, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SetHeadRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +SetHeadRequest<librbd::MockTestImageCtx>* SetHeadRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace deep_copy +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/SnapshotCreateRequest.cc" +template class librbd::deep_copy::SnapshotCreateRequest<librbd::MockTestImageCtx>; + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; +using ::testing::ReturnNew; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockDeepCopySnapshotCreateRequest : public TestMockFixture { +public: + typedef SetHeadRequest<librbd::MockTestImageCtx> MockSetHeadRequest; + typedef SnapshotCreateRequest<librbd::MockTestImageCtx> MockSnapshotCreateRequest; + + librbd::ImageCtx *m_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_image_ctx->cct, &m_thread_pool, + &m_work_queue); + } + + void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) { + EXPECT_CALL(mock_exclusive_lock, start_op(_)).WillOnce( + ReturnNew<FunctionContext>([](int) {})); + } + + void expect_test_features(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t features, bool enabled) { + EXPECT_CALL(mock_image_ctx, test_features(features)) + .WillOnce(Return(enabled)); + } + + void expect_set_head(MockSetHeadRequest &mock_set_head_request, int r) { + EXPECT_CALL(mock_set_head_request, send()) + .WillOnce(Invoke([&mock_set_head_request, r]() { + mock_set_head_request.on_finish->complete(r); + })); + } + + void expect_snap_create(librbd::MockTestImageCtx &mock_image_ctx, + const std::string &snap_name, uint64_t snap_id, int r) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_create(_, StrEq(snap_name), _, 0, true)) + .WillOnce(DoAll(InvokeWithoutArgs([&mock_image_ctx, snap_id, snap_name]() { + inject_snap(mock_image_ctx, snap_id, snap_name); + }), + WithArg<2>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + })))); + } + + void expect_object_map_resize(librbd::MockTestImageCtx &mock_image_ctx, + librados::snap_t snap_id, int r) { + std::string oid(librbd::ObjectMap<>::object_map_name(mock_image_ctx.id, + snap_id)); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_resize"), _, _, _)) + .WillOnce(Return(r)); + } + + static void inject_snap(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, const std::string &snap_name) { + mock_image_ctx.snap_ids[{cls::rbd::UserSnapshotNamespace(), + snap_name}] = snap_id; + } + + MockSnapshotCreateRequest *create_request(librbd::MockTestImageCtx &mock_local_image_ctx, + const std::string &snap_name, + const cls::rbd::SnapshotNamespace &snap_namespace, + uint64_t size, + const cls::rbd::ParentImageSpec &spec, + uint64_t parent_overlap, + Context *on_finish) { + return new MockSnapshotCreateRequest(&mock_local_image_ctx, snap_name, snap_namespace, size, + spec, parent_overlap, on_finish); + } +}; + +TEST_F(TestMockDeepCopySnapshotCreateRequest, SnapCreate) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + MockSetHeadRequest mock_set_head_request; + + InSequence seq; + expect_set_head(mock_set_head_request, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_image_ctx, "snap1", 10, 0); + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, false); + + C_SaferCond ctx; + MockSnapshotCreateRequest *request = create_request(mock_image_ctx, + "snap1", + cls::rbd::UserSnapshotNamespace(), + m_image_ctx->size, + {}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCreateRequest, SetHeadError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + MockSetHeadRequest mock_set_head_request; + + InSequence seq; + expect_set_head(mock_set_head_request, -EINVAL); + + C_SaferCond ctx; + MockSnapshotCreateRequest *request = create_request(mock_image_ctx, + "snap1", + cls::rbd::UserSnapshotNamespace(), + 123, {}, 0, + &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCreateRequest, SnapCreateError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + MockSetHeadRequest mock_set_head_request; + + InSequence seq; + expect_set_head(mock_set_head_request, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_image_ctx, "snap1", 10, -EINVAL); + + C_SaferCond ctx; + MockSnapshotCreateRequest *request = create_request(mock_image_ctx, + "snap1", + cls::rbd::UserSnapshotNamespace(), + m_image_ctx->size, + {}, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCreateRequest, ResizeObjectMap) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + MockSetHeadRequest mock_set_head_request; + + InSequence seq; + expect_set_head(mock_set_head_request, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_image_ctx, "snap1", 10, 0); + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true); + expect_start_op(mock_exclusive_lock); + expect_object_map_resize(mock_image_ctx, 10, 0); + + C_SaferCond ctx; + MockSnapshotCreateRequest *request = create_request(mock_image_ctx, + "snap1", + cls::rbd::UserSnapshotNamespace(), + m_image_ctx->size, + {}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCreateRequest, ResizeObjectMapError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + MockSetHeadRequest mock_set_head_request; + + InSequence seq; + expect_set_head(mock_set_head_request, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_image_ctx, "snap1", 10, 0); + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true); + expect_start_op(mock_exclusive_lock); + expect_object_map_resize(mock_image_ctx, 10, -EINVAL); + + C_SaferCond ctx; + MockSnapshotCreateRequest *request = create_request(mock_image_ctx, + "snap1", + cls::rbd::UserSnapshotNamespace(), + m_image_ctx->size, + {}, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace deep_copy +} // namespace librbd diff --git a/src/test/librbd/exclusive_lock/test_mock_PostAcquireRequest.cc b/src/test/librbd/exclusive_lock/test_mock_PostAcquireRequest.cc new file mode 100644 index 00000000..b38fbed9 --- /dev/null +++ b/src/test/librbd/exclusive_lock/test_mock_PostAcquireRequest.cc @@ -0,0 +1,514 @@ +// -*- 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/MockImageState.h" +#include "test/librbd/mock/MockJournal.h" +#include "test/librbd/mock/MockJournalPolicy.h" +#include "test/librbd/mock/MockObjectMap.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/exclusive_lock/PostAcquireRequest.h" +#include "librbd/image/RefreshRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <list> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +inline ImageCtx &get_image_ctx(MockTestImageCtx &image_ctx) { + return *(image_ctx.image_ctx); +} + +} // anonymous namespace + +namespace image { + +template<> +struct RefreshRequest<librbd::MockTestImageCtx> { + static RefreshRequest *s_instance; + Context *on_finish = nullptr; + + static RefreshRequest *create(librbd::MockTestImageCtx &image_ctx, + bool acquire_lock_refresh, + bool skip_open_parent, Context *on_finish) { + EXPECT_TRUE(acquire_lock_refresh); + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + RefreshRequest() { + s_instance = this; + } + MOCK_METHOD0(send, void()); +}; + +RefreshRequest<librbd::MockTestImageCtx> *RefreshRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace image +} // namespace librbd + +// template definitions +#include "librbd/Journal.cc" + +#include "librbd/exclusive_lock/PostAcquireRequest.cc" +template class librbd::exclusive_lock::PostAcquireRequest<librbd::MockTestImageCtx>; + +ACTION_P3(FinishRequest2, request, r, mock) { + mock->image_ctx->op_work_queue->queue(request->on_finish, r); +} + + +namespace librbd { +namespace exclusive_lock { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +static const std::string TEST_COOKIE("auto 123"); + +class TestMockExclusiveLockPostAcquireRequest : public TestMockFixture { +public: + typedef PostAcquireRequest<MockTestImageCtx> MockPostAcquireRequest; + typedef librbd::image::RefreshRequest<MockTestImageCtx> MockRefreshRequest; + + void expect_test_features(MockTestImageCtx &mock_image_ctx, uint64_t features, + bool enabled) { + EXPECT_CALL(mock_image_ctx, test_features(features)) + .WillOnce(Return(enabled)); + } + + void expect_test_features(MockTestImageCtx &mock_image_ctx, uint64_t features, + RWLock &lock, bool enabled) { + EXPECT_CALL(mock_image_ctx, test_features(features, _)) + .WillOnce(Return(enabled)); + } + + void expect_is_refresh_required(MockTestImageCtx &mock_image_ctx, bool required) { + EXPECT_CALL(*mock_image_ctx.state, is_refresh_required()) + .WillOnce(Return(required)); + } + + void expect_refresh(MockTestImageCtx &mock_image_ctx, + MockRefreshRequest &mock_refresh_request, int r) { + EXPECT_CALL(mock_refresh_request, send()) + .WillOnce(FinishRequest2(&mock_refresh_request, r, + &mock_image_ctx)); + } + + void expect_create_object_map(MockTestImageCtx &mock_image_ctx, + MockObjectMap *mock_object_map) { + EXPECT_CALL(mock_image_ctx, create_object_map(_)) + .WillOnce(Return(mock_object_map)); + } + + void expect_open_object_map(MockTestImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map, int r) { + EXPECT_CALL(mock_object_map, open(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_close_object_map(MockTestImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map) { + EXPECT_CALL(mock_object_map, close(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_create_journal(MockTestImageCtx &mock_image_ctx, + MockJournal *mock_journal) { + EXPECT_CALL(mock_image_ctx, create_journal()) + .WillOnce(Return(mock_journal)); + } + + void expect_open_journal(MockTestImageCtx &mock_image_ctx, + MockJournal &mock_journal, int r) { + EXPECT_CALL(mock_journal, open(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_close_journal(MockTestImageCtx &mock_image_ctx, + MockJournal &mock_journal) { + EXPECT_CALL(mock_journal, close(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_get_journal_policy(MockTestImageCtx &mock_image_ctx, + MockJournalPolicy &mock_journal_policy) { + EXPECT_CALL(mock_image_ctx, get_journal_policy()) + .WillOnce(Return(&mock_journal_policy)); + } + + void expect_journal_disabled(MockJournalPolicy &mock_journal_policy, + bool disabled) { + EXPECT_CALL(mock_journal_policy, journal_disabled()) + .WillOnce(Return(disabled)); + } + + void expect_allocate_journal_tag(MockTestImageCtx &mock_image_ctx, + MockJournalPolicy &mock_journal_policy, + int r) { + EXPECT_CALL(mock_journal_policy, allocate_tag_on_lock(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_handle_prepare_lock_complete(MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, handle_prepare_lock_complete()); + } + +}; + +TEST_F(TestMockExclusiveLockPostAcquireRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_is_refresh_required(mock_image_ctx, false); + + MockObjectMap mock_object_map; + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true); + expect_create_object_map(mock_image_ctx, &mock_object_map); + expect_open_object_map(mock_image_ctx, mock_object_map, 0); + + MockJournal mock_journal; + MockJournalPolicy mock_journal_policy; + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, + mock_image_ctx.snap_lock, true); + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_journal_disabled(mock_journal_policy, false); + expect_create_journal(mock_image_ctx, &mock_journal); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_open_journal(mock_image_ctx, mock_journal, 0); + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_allocate_journal_tag(mock_image_ctx, mock_journal_policy, 0); + + C_SaferCond acquire_ctx; + C_SaferCond ctx; + MockPostAcquireRequest *req = MockPostAcquireRequest::create(mock_image_ctx, + &acquire_ctx, + &ctx); + req->send(); + ASSERT_EQ(0, acquire_ctx.wait()); + ASSERT_EQ(0, ctx.wait()); + } + +TEST_F(TestMockExclusiveLockPostAcquireRequest, SuccessRefresh) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockRefreshRequest mock_refresh_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_is_refresh_required(mock_image_ctx, true); + expect_refresh(mock_image_ctx, mock_refresh_request, 0); + + MockObjectMap mock_object_map; + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, false); + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, + mock_image_ctx.snap_lock, false); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond acquire_ctx; + C_SaferCond ctx; + MockPostAcquireRequest *req = MockPostAcquireRequest::create(mock_image_ctx, + &acquire_ctx, + &ctx); + req->send(); + ASSERT_EQ(0, acquire_ctx.wait()); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPostAcquireRequest, SuccessJournalDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_is_refresh_required(mock_image_ctx, false); + + MockObjectMap mock_object_map; + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true); + expect_create_object_map(mock_image_ctx, &mock_object_map); + expect_open_object_map(mock_image_ctx, mock_object_map, 0); + + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, + mock_image_ctx.snap_lock, false); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond acquire_ctx; + C_SaferCond ctx; + MockPostAcquireRequest *req = MockPostAcquireRequest::create(mock_image_ctx, + &acquire_ctx, + &ctx); + req->send(); + ASSERT_EQ(0, acquire_ctx.wait()); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPostAcquireRequest, SuccessObjectMapDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_is_refresh_required(mock_image_ctx, false); + + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, false); + + MockJournal mock_journal; + MockJournalPolicy mock_journal_policy; + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, + mock_image_ctx.snap_lock, true); + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_journal_disabled(mock_journal_policy, false); + expect_create_journal(mock_image_ctx, &mock_journal); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_open_journal(mock_image_ctx, mock_journal, 0); + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_allocate_journal_tag(mock_image_ctx, mock_journal_policy, 0); + + C_SaferCond acquire_ctx; + C_SaferCond ctx; + MockPostAcquireRequest *req = MockPostAcquireRequest::create(mock_image_ctx, + &acquire_ctx, + &ctx); + req->send(); + ASSERT_EQ(0, acquire_ctx.wait()); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPostAcquireRequest, RefreshError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockRefreshRequest mock_refresh_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_is_refresh_required(mock_image_ctx, true); + expect_refresh(mock_image_ctx, mock_refresh_request, -EINVAL); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond *acquire_ctx = new C_SaferCond(); + C_SaferCond ctx; + MockPostAcquireRequest *req = MockPostAcquireRequest::create(mock_image_ctx, + acquire_ctx, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPostAcquireRequest, RefreshLockDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockRefreshRequest mock_refresh_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_is_refresh_required(mock_image_ctx, true); + expect_refresh(mock_image_ctx, mock_refresh_request, -ERESTART); + + MockObjectMap mock_object_map; + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, false); + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, + mock_image_ctx.snap_lock, false); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond acquire_ctx; + C_SaferCond ctx; + MockPostAcquireRequest *req = MockPostAcquireRequest::create(mock_image_ctx, + &acquire_ctx, + &ctx); + req->send(); + ASSERT_EQ(0, acquire_ctx.wait()); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPostAcquireRequest, JournalError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_is_refresh_required(mock_image_ctx, false); + + MockObjectMap *mock_object_map = new MockObjectMap(); + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true); + expect_create_object_map(mock_image_ctx, mock_object_map); + expect_open_object_map(mock_image_ctx, *mock_object_map, 0); + + MockJournal *mock_journal = new MockJournal(); + MockJournalPolicy mock_journal_policy; + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, + mock_image_ctx.snap_lock, true); + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_journal_disabled(mock_journal_policy, false); + expect_create_journal(mock_image_ctx, mock_journal); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_open_journal(mock_image_ctx, *mock_journal, -EINVAL); + expect_close_journal(mock_image_ctx, *mock_journal); + expect_close_object_map(mock_image_ctx, *mock_object_map); + + C_SaferCond acquire_ctx; + C_SaferCond ctx; + MockPostAcquireRequest *req = MockPostAcquireRequest::create(mock_image_ctx, + &acquire_ctx, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPostAcquireRequest, AllocateJournalTagError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_is_refresh_required(mock_image_ctx, false); + + MockObjectMap *mock_object_map = new MockObjectMap(); + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true); + expect_create_object_map(mock_image_ctx, mock_object_map); + expect_open_object_map(mock_image_ctx, *mock_object_map, 0); + + MockJournal *mock_journal = new MockJournal(); + MockJournalPolicy mock_journal_policy; + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, + mock_image_ctx.snap_lock, true); + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_journal_disabled(mock_journal_policy, false); + expect_create_journal(mock_image_ctx, mock_journal); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_open_journal(mock_image_ctx, *mock_journal, 0); + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_allocate_journal_tag(mock_image_ctx, mock_journal_policy, -EPERM); + expect_close_journal(mock_image_ctx, *mock_journal); + expect_close_object_map(mock_image_ctx, *mock_object_map); + + C_SaferCond acquire_ctx; + C_SaferCond ctx; + MockPostAcquireRequest *req = MockPostAcquireRequest::create(mock_image_ctx, + &acquire_ctx, + &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPostAcquireRequest, OpenObjectMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_is_refresh_required(mock_image_ctx, false); + + MockObjectMap *mock_object_map = new MockObjectMap(); + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true); + expect_create_object_map(mock_image_ctx, mock_object_map); + expect_open_object_map(mock_image_ctx, *mock_object_map, -EINVAL); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond *acquire_ctx = new C_SaferCond(); + C_SaferCond ctx; + MockPostAcquireRequest *req = MockPostAcquireRequest::create(mock_image_ctx, + acquire_ctx, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); + ASSERT_EQ(nullptr, mock_image_ctx.object_map); +} + +TEST_F(TestMockExclusiveLockPostAcquireRequest, OpenObjectMapTooBig) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_is_refresh_required(mock_image_ctx, false); + + MockObjectMap *mock_object_map = new MockObjectMap(); + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true); + expect_create_object_map(mock_image_ctx, mock_object_map); + expect_open_object_map(mock_image_ctx, *mock_object_map, -EFBIG); + + MockJournal mock_journal; + MockJournalPolicy mock_journal_policy; + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, + mock_image_ctx.snap_lock, true); + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_journal_disabled(mock_journal_policy, false); + expect_create_journal(mock_image_ctx, &mock_journal); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_open_journal(mock_image_ctx, mock_journal, 0); + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_allocate_journal_tag(mock_image_ctx, mock_journal_policy, 0); + + C_SaferCond acquire_ctx; + C_SaferCond ctx; + MockPostAcquireRequest *req = MockPostAcquireRequest::create(mock_image_ctx, + &acquire_ctx, + &ctx); + req->send(); + ASSERT_EQ(0, acquire_ctx.wait()); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(nullptr, mock_image_ctx.object_map); +} + +} // namespace exclusive_lock +} // namespace librbd diff --git a/src/test/librbd/exclusive_lock/test_mock_PreAcquireRequest.cc b/src/test/librbd/exclusive_lock/test_mock_PreAcquireRequest.cc new file mode 100644 index 00000000..5b4bce6d --- /dev/null +++ b/src/test/librbd/exclusive_lock/test_mock_PreAcquireRequest.cc @@ -0,0 +1,92 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/exclusive_lock/PreAcquireRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <list> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +inline ImageCtx &get_image_ctx(MockTestImageCtx &image_ctx) { + return *(image_ctx.image_ctx); +} + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/exclusive_lock/PreAcquireRequest.cc" +template class librbd::exclusive_lock::PreAcquireRequest<librbd::MockTestImageCtx>; + +namespace librbd { +namespace exclusive_lock { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +static const std::string TEST_COOKIE("auto 123"); + +class TestMockExclusiveLockPreAcquireRequest : public TestMockFixture { +public: + typedef PreAcquireRequest<MockTestImageCtx> MockPreAcquireRequest; + + void expect_flush_notifies(MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.image_watcher, flush(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_prepare_lock(MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, prepare_lock(_)) + .WillOnce(Invoke([](Context *on_ready) { + on_ready->complete(0); + })); + } + + void expect_handle_prepare_lock_complete(MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, handle_prepare_lock_complete()); + } + +}; + +TEST_F(TestMockExclusiveLockPreAcquireRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_flush_notifies(mock_image_ctx); + + C_SaferCond ctx; + MockPreAcquireRequest *req = MockPreAcquireRequest::create(mock_image_ctx, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + } + +} // namespace exclusive_lock +} // namespace librbd diff --git a/src/test/librbd/exclusive_lock/test_mock_PreReleaseRequest.cc b/src/test/librbd/exclusive_lock/test_mock_PreReleaseRequest.cc new file mode 100644 index 00000000..989e793f --- /dev/null +++ b/src/test/librbd/exclusive_lock/test_mock_PreReleaseRequest.cc @@ -0,0 +1,295 @@ +// -*- 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/MockJournal.h" +#include "test/librbd/mock/MockObjectMap.h" +#include "test/librbd/mock/io/MockObjectDispatch.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/AsyncOpTracker.h" +#include "librbd/exclusive_lock/PreReleaseRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <list> + +// template definitions +#include "librbd/exclusive_lock/PreReleaseRequest.cc" +template class librbd::exclusive_lock::PreReleaseRequest<librbd::MockImageCtx>; + +namespace librbd { + +namespace exclusive_lock { + +namespace { + +struct MockContext : public Context { + MOCK_METHOD1(complete, void(int)); + MOCK_METHOD1(finish, void(int)); +}; + +} // anonymous namespace + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +static const std::string TEST_COOKIE("auto 123"); + +class TestMockExclusiveLockPreReleaseRequest : public TestMockFixture { +public: + typedef PreReleaseRequest<MockImageCtx> MockPreReleaseRequest; + + void expect_complete_context(MockContext &mock_context, int r) { + EXPECT_CALL(mock_context, complete(r)); + } + + void expect_test_features(MockImageCtx &mock_image_ctx, uint64_t features, + bool enabled) { + EXPECT_CALL(mock_image_ctx, test_features(features)) + .WillOnce(Return(enabled)); + } + + void expect_set_require_lock(MockImageCtx &mock_image_ctx, + librbd::io::Direction direction, bool enabled) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, set_require_lock(direction, + enabled)); + } + + void expect_block_writes(MockImageCtx &mock_image_ctx, int r) { + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, + ((mock_image_ctx.features & RBD_FEATURE_JOURNALING) != 0)); + if (mock_image_ctx.clone_copy_on_read || + (mock_image_ctx.features & RBD_FEATURE_JOURNALING) != 0) { + expect_set_require_lock(mock_image_ctx, librbd::io::DIRECTION_BOTH, true); + } else { + expect_set_require_lock(mock_image_ctx, librbd::io::DIRECTION_WRITE, + true); + } + EXPECT_CALL(*mock_image_ctx.io_work_queue, block_writes(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_unblock_writes(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, unblock_writes()); + } + + void expect_cancel_op_requests(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(mock_image_ctx, cancel_async_requests(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_close_journal(MockImageCtx &mock_image_ctx, + MockJournal &mock_journal, int r) { + EXPECT_CALL(mock_journal, close(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_close_object_map(MockImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map) { + EXPECT_CALL(mock_object_map, close(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_invalidate_cache(MockImageCtx &mock_image_ctx, + int r) { + EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, invalidate_cache(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_flush_notifies(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.image_watcher, flush(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_prepare_lock(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, prepare_lock(_)) + .WillOnce(Invoke([](Context *on_ready) { + on_ready->complete(0); + })); + } + + void expect_handle_prepare_lock_complete(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, handle_prepare_lock_complete()); + } + + AsyncOpTracker m_async_op_tracker; +}; + +TEST_F(TestMockExclusiveLockPreReleaseRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + expect_prepare_lock(mock_image_ctx); + expect_cancel_op_requests(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx, 0); + expect_invalidate_cache(mock_image_ctx, 0); + + expect_flush_notifies(mock_image_ctx); + + MockJournal *mock_journal = new MockJournal(); + mock_image_ctx.journal = mock_journal; + expect_close_journal(mock_image_ctx, *mock_journal, -EINVAL); + + MockObjectMap *mock_object_map = new MockObjectMap(); + mock_image_ctx.object_map = mock_object_map; + expect_close_object_map(mock_image_ctx, *mock_object_map); + + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond ctx; + MockPreReleaseRequest *req = MockPreReleaseRequest::create( + mock_image_ctx, false, m_async_op_tracker, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPreReleaseRequest, SuccessJournalDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_block_writes(mock_image_ctx, 0); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_cancel_op_requests(mock_image_ctx, 0); + expect_invalidate_cache(mock_image_ctx, 0); + + expect_flush_notifies(mock_image_ctx); + + MockObjectMap *mock_object_map = new MockObjectMap(); + mock_image_ctx.object_map = mock_object_map; + expect_close_object_map(mock_image_ctx, *mock_object_map); + + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond ctx; + MockPreReleaseRequest *req = MockPreReleaseRequest::create( + mock_image_ctx, false, m_async_op_tracker, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPreReleaseRequest, SuccessObjectMapDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_block_writes(mock_image_ctx, 0); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_cancel_op_requests(mock_image_ctx, 0); + expect_invalidate_cache(mock_image_ctx, 0); + + expect_flush_notifies(mock_image_ctx); + + C_SaferCond release_ctx; + C_SaferCond ctx; + MockPreReleaseRequest *req = MockPreReleaseRequest::create( + mock_image_ctx, true, m_async_op_tracker, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPreReleaseRequest, Blacklisted) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_cancel_op_requests(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx, -EBLACKLISTED); + expect_invalidate_cache(mock_image_ctx, -EBLACKLISTED); + + expect_flush_notifies(mock_image_ctx); + + MockJournal *mock_journal = new MockJournal(); + mock_image_ctx.journal = mock_journal; + expect_close_journal(mock_image_ctx, *mock_journal, -EBLACKLISTED); + + MockObjectMap *mock_object_map = new MockObjectMap(); + mock_image_ctx.object_map = mock_object_map; + expect_close_object_map(mock_image_ctx, *mock_object_map); + + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond ctx; + MockPreReleaseRequest *req = MockPreReleaseRequest::create( + mock_image_ctx, false, m_async_op_tracker, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPreReleaseRequest, BlockWritesError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_cancel_op_requests(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond ctx; + MockPreReleaseRequest *req = MockPreReleaseRequest::create( + mock_image_ctx, true, m_async_op_tracker, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockExclusiveLockPreReleaseRequest, UnlockError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_cancel_op_requests(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx, 0); + expect_invalidate_cache(mock_image_ctx, 0); + + expect_flush_notifies(mock_image_ctx); + + C_SaferCond ctx; + MockPreReleaseRequest *req = MockPreReleaseRequest::create( + mock_image_ctx, true, m_async_op_tracker, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace exclusive_lock +} // namespace librbd diff --git a/src/test/librbd/fsx.cc b/src/test/librbd/fsx.cc new file mode 100644 index 00000000..8b8c42fc --- /dev/null +++ b/src/test/librbd/fsx.cc @@ -0,0 +1,3448 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:8; indent-tabs-mode:t -*- +// vim: ts=8 sw=8 smarttab +/* + * Copyright (C) 1991, NeXT Computer, Inc. All Rights Reserverd. + * + * File: fsx.cc + * Author: Avadis Tevanian, Jr. + * + * File system exerciser. + * + * Rewritten 8/98 by Conrad Minshall. + * + * Small changes to work under Linux -- davej. + * + * Checks for mmap last-page zero fill. + */ + +#include <sys/types.h> +#include <unistd.h> +#include <getopt.h> +#include <limits.h> +#include <strings.h> +#if defined(__FreeBSD__) +#include <sys/disk.h> +#endif +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/mman.h> +#if defined(__linux__) +#include <linux/fs.h> +#endif +#include <sys/ioctl.h> +#ifdef HAVE_ERR_H +#include <err.h> +#endif +#include <signal.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <assert.h> +#include <errno.h> +#include <math.h> +#include <fcntl.h> +#include <random> + +#include "include/compat.h" +#include "include/intarith.h" +#if defined(WITH_KRBD) +#include "include/krbd.h" +#endif +#include "include/rados/librados.h" +#include "include/rados/librados.hpp" +#include "include/rbd/librbd.h" +#include "include/rbd/librbd.hpp" +#include "common/Cond.h" +#include "common/SubProcess.h" +#include "common/safe_io.h" +#include "journal/Journaler.h" +#include "journal/ReplayEntry.h" +#include "journal/ReplayHandler.h" +#include "journal/Settings.h" + +#include <boost/scope_exit.hpp> + +#define NUMPRINTCOLUMNS 32 /* # columns of data to print on each line */ + +/* + * A log entry is an operation and a bunch of arguments. + */ + +struct log_entry { + int operation; + int args[3]; +}; + +#define LOGSIZE 1000 + +struct log_entry oplog[LOGSIZE]; /* the log */ +int logptr = 0; /* current position in log */ +int logcount = 0; /* total ops */ + +/* + * The operation matrix is complex due to conditional execution of different + * features. Hence when we come to deciding what operation to run, we need to + * be careful in how we select the different operations. The active operations + * are mapped to numbers as follows: + * + * lite !lite + * READ: 0 0 + * WRITE: 1 1 + * MAPREAD: 2 2 + * MAPWRITE: 3 3 + * TRUNCATE: - 4 + * FALLOCATE: - 5 + * PUNCH HOLE: - 6 + * WRITESAME: - 7 + * COMPAREANDWRITE: - 8 + * + * When mapped read/writes are disabled, they are simply converted to normal + * reads and writes. When fallocate/fpunch calls are disabled, they are + * converted to OP_SKIPPED. Hence OP_SKIPPED needs to have a number higher than + * the operation selction matrix, as does the OP_CLOSEOPEN which is an + * operation modifier rather than an operation in itself. + * + * Because of the "lite" version, we also need to have different "maximum + * operation" defines to allow the ops to be selected correctly based on the + * mode being run. + */ + +/* common operations */ +#define OP_READ 0 +#define OP_WRITE 1 +#define OP_MAPREAD 2 +#define OP_MAPWRITE 3 +#define OP_MAX_LITE 4 + +/* !lite operations */ +#define OP_TRUNCATE 4 +#define OP_FALLOCATE 5 +#define OP_PUNCH_HOLE 6 +#define OP_WRITESAME 7 +#define OP_COMPARE_AND_WRITE 8 +/* rbd-specific operations */ +#define OP_CLONE 9 +#define OP_FLATTEN 10 +#define OP_MAX_FULL 11 + +/* operation modifiers */ +#define OP_CLOSEOPEN 100 +#define OP_SKIPPED 101 + +#undef PAGE_SIZE +#define PAGE_SIZE getpagesize() +#undef PAGE_MASK +#define PAGE_MASK (PAGE_SIZE - 1) + + +char *original_buf; /* a pointer to the original data */ +char *good_buf; /* a pointer to the correct data */ +char *temp_buf; /* a pointer to the current data */ + +char dirpath[1024]; + +off_t file_size = 0; +off_t biggest = 0; +unsigned long testcalls = 0; /* calls to function "test" */ + +const char* cluster_name = "ceph"; /* --cluster optional */ +const char* client_id = "admin"; /* --id optional */ + +unsigned long simulatedopcount = 0; /* -b flag */ +int closeprob = 0; /* -c flag */ +int debug = 0; /* -d flag */ +unsigned long debugstart = 0; /* -D flag */ +int flush_enabled = 0; /* -f flag */ +int deep_copy = 0; /* -g flag */ +int holebdy = 1; /* -h flag */ +bool journal_replay = false; /* -j flah */ +int keep_on_success = 0; /* -k flag */ +int do_fsync = 0; /* -y flag */ +unsigned long maxfilelen = 256 * 1024; /* -l flag */ +int sizechecks = 1; /* -n flag disables them */ +int maxoplen = 64 * 1024; /* -o flag */ +int quiet = 0; /* -q flag */ +unsigned long progressinterval = 0; /* -p flag */ +int readbdy = 1; /* -r flag */ +int style = 0; /* -s flag */ +int prealloc = 0; /* -x flag */ +int truncbdy = 1; /* -t flag */ +int writebdy = 1; /* -w flag */ +long monitorstart = -1; /* -m flag */ +long monitorend = -1; /* -m flag */ +int lite = 0; /* -L flag */ +long numops = -1; /* -N flag */ +int randomoplen = 1; /* -O flag disables it */ +int seed = 1; /* -S flag */ +int mapped_writes = 0; /* -W flag disables */ +int fallocate_calls = 0; /* -F flag disables */ +int punch_hole_calls = 1; /* -H flag disables */ +int clone_calls = 1; /* -C flag disables */ +int randomize_striping = 1; /* -U flag disables */ +int randomize_parent_overlap = 1; +int mapped_reads = 0; /* -R flag disables it */ +int fsxgoodfd = 0; +int o_direct = 0; /* -Z flag */ + +int num_clones = 0; + +int page_size; +int page_mask; +int mmap_mask; + +FILE * fsxlogf = NULL; +int badoff = -1; +int closeopen = 0; + +void +vwarnc(int code, const char *fmt, va_list ap) { + fprintf(stderr, "fsx: "); + if (fmt != NULL) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": "); + } + fprintf(stderr, "%s\n", strerror(code)); +} + +void +warn(const char * fmt, ...) { + va_list ap; + va_start(ap, fmt); + vwarnc(errno, fmt, ap); + va_end(ap); +} + +#define BUF_SIZE 1024 + +void +prt(const char *fmt, ...) +{ + va_list args; + char buffer[BUF_SIZE]; + + va_start(args, fmt); + vsnprintf(buffer, BUF_SIZE, fmt, args); + va_end(args); + fprintf(stdout, "%s", buffer); + if (fsxlogf) + fprintf(fsxlogf, "%s", buffer); +} + +void +prterr(const char *prefix) +{ + prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(errno)); +} + +void +prterrcode(const char *prefix, int code) +{ + prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(-code)); +} + +void +simple_err(const char *msg, int err) +{ + fprintf(stderr, "%s: %s\n", msg, strerror(-err)); +} + +/* + * random + */ +std::mt19937 random_generator; + +uint_fast32_t +get_random(void) +{ + return random_generator(); +} + +int get_features(uint64_t* features); +void replay_imagename(char *buf, size_t len, int clones); + +namespace { + +static const std::string JOURNAL_CLIENT_ID("fsx"); + +struct ReplayHandler : public journal::ReplayHandler { + journal::Journaler *journaler; + journal::Journaler *replay_journaler; + Context *on_finish; + + ReplayHandler(journal::Journaler *journaler, + journal::Journaler *replay_journaler, Context *on_finish) + : journaler(journaler), replay_journaler(replay_journaler), + on_finish(on_finish) { + } + + void get() override { + } + void put() override { + } + + void handle_entries_available() override { + while (true) { + journal::ReplayEntry replay_entry; + if (!journaler->try_pop_front(&replay_entry)) { + return; + } + + replay_journaler->append(0, replay_entry.get_data()); + } + } + + void handle_complete(int r) override { + on_finish->complete(r); + } +}; + +int get_image_id(librados::IoCtx &io_ctx, const char *image_name, + std::string *image_id) { + librbd::RBD rbd; + librbd::Image image; + int r = rbd.open(io_ctx, image, image_name); + if (r < 0) { + simple_err("failed to open image", r); + return r; + } + + rbd_image_info_t info; + r = image.stat(info, sizeof(info)); + if (r < 0) { + simple_err("failed to stat image", r); + return r; + } + + *image_id = std::string(&info.block_name_prefix[strlen(RBD_DATA_PREFIX)]); + return 0; +} + +int register_journal(rados_ioctx_t ioctx, const char *image_name) { + librados::IoCtx io_ctx; + librados::IoCtx::from_rados_ioctx_t(ioctx, io_ctx); + + std::string image_id; + int r = get_image_id(io_ctx, image_name, &image_id); + if (r < 0) { + return r; + } + + journal::Journaler journaler(io_ctx, image_id, JOURNAL_CLIENT_ID, {}); + r = journaler.register_client(bufferlist()); + if (r < 0) { + simple_err("failed to register journal client", r); + return r; + } + return 0; +} + +int unregister_journal(rados_ioctx_t ioctx, const char *image_name) { + librados::IoCtx io_ctx; + librados::IoCtx::from_rados_ioctx_t(ioctx, io_ctx); + + std::string image_id; + int r = get_image_id(io_ctx, image_name, &image_id); + if (r < 0) { + return r; + } + + journal::Journaler journaler(io_ctx, image_id, JOURNAL_CLIENT_ID, {}); + r = journaler.unregister_client(); + if (r < 0) { + simple_err("failed to unregister journal client", r); + return r; + } + return 0; +} + +int create_replay_image(rados_ioctx_t ioctx, int order, + uint64_t stripe_unit, int stripe_count, + const char *replay_image_name, + const char *last_replay_image_name) { + librados::IoCtx io_ctx; + librados::IoCtx::from_rados_ioctx_t(ioctx, io_ctx); + + uint64_t features; + int r = get_features(&features); + if (r < 0) { + return r; + } + + librbd::RBD rbd; + if (last_replay_image_name == nullptr) { + r = rbd.create2(io_ctx, replay_image_name, 0, features, &order); + } else { + r = rbd.clone2(io_ctx, last_replay_image_name, "snap", + io_ctx, replay_image_name, features, &order, + stripe_unit, stripe_count); + } + + if (r < 0) { + simple_err("failed to create replay image", r); + return r; + } + + return 0; +} + +int replay_journal(rados_ioctx_t ioctx, const char *image_name, + const char *replay_image_name) { + librados::IoCtx io_ctx; + librados::IoCtx::from_rados_ioctx_t(ioctx, io_ctx); + + std::string image_id; + int r = get_image_id(io_ctx, image_name, &image_id); + if (r < 0) { + return r; + } + + std::string replay_image_id; + r = get_image_id(io_ctx, replay_image_name, &replay_image_id); + if (r < 0) { + return r; + } + + journal::Journaler journaler(io_ctx, image_id, JOURNAL_CLIENT_ID, {}); + C_SaferCond init_ctx; + journaler.init(&init_ctx); + BOOST_SCOPE_EXIT_ALL( (&journaler) ) { + journaler.shut_down(); + }; + + r = init_ctx.wait(); + if (r < 0) { + simple_err("failed to initialize journal", r); + return r; + } + + journal::Journaler replay_journaler(io_ctx, replay_image_id, "", {}); + + C_SaferCond replay_init_ctx; + replay_journaler.init(&replay_init_ctx); + BOOST_SCOPE_EXIT_ALL( (&replay_journaler) ) { + replay_journaler.shut_down(); + }; + + r = replay_init_ctx.wait(); + if (r < 0) { + simple_err("failed to initialize replay journal", r); + return r; + } + + replay_journaler.start_append(0); + + C_SaferCond replay_ctx; + ReplayHandler replay_handler(&journaler, &replay_journaler, + &replay_ctx); + + // copy journal events from source image to replay image + journaler.start_replay(&replay_handler); + r = replay_ctx.wait(); + + journaler.stop_replay(); + + C_SaferCond stop_ctx; + replay_journaler.stop_append(&stop_ctx); + int stop_r = stop_ctx.wait(); + if (r == 0 && stop_r < 0) { + r = stop_r; + } + + if (r < 0) { + simple_err("failed to replay journal", r); + return r; + } + + librbd::RBD rbd; + librbd::Image image; + r = rbd.open(io_ctx, image, replay_image_name); + if (r < 0) { + simple_err("failed to open replay image", r); + return r; + } + + // perform an IO op to initiate the journal replay + bufferlist bl; + r = static_cast<ssize_t>(image.write(0, 0, bl)); + if (r < 0) { + simple_err("failed to write to replay image", r); + return r; + } + return 0; +} + +int finalize_journal(rados_ioctx_t ioctx, const char *imagename, int clones, + int order, uint64_t stripe_unit, int stripe_count) { + char replayimagename[1024]; + replay_imagename(replayimagename, sizeof(replayimagename), clones); + + char lastreplayimagename[1024]; + if (clones > 0) { + replay_imagename(lastreplayimagename, + sizeof(lastreplayimagename), clones - 1); + } + + int ret = create_replay_image(ioctx, order, stripe_unit, + stripe_count, replayimagename, + clones > 0 ? lastreplayimagename : + nullptr); + if (ret < 0) { + exit(EXIT_FAILURE); + } + + ret = replay_journal(ioctx, imagename, replayimagename); + if (ret < 0) { + exit(EXIT_FAILURE); + } + return 0; +} + +} // anonymous namespace + +/* + * rbd + */ + +struct rbd_ctx { + const char *name; /* image name */ + rbd_image_t image; /* image handle */ + const char *krbd_name; /* image /dev/rbd<id> name */ /* reused for nbd test */ + int krbd_fd; /* image /dev/rbd<id> fd */ /* reused for nbd test */ +}; + +#define RBD_CTX_INIT (struct rbd_ctx) { NULL, NULL, NULL, -1} + +struct rbd_operations { + int (*open)(const char *name, struct rbd_ctx *ctx); + int (*close)(struct rbd_ctx *ctx); + ssize_t (*read)(struct rbd_ctx *ctx, uint64_t off, size_t len, char *buf); + ssize_t (*write)(struct rbd_ctx *ctx, uint64_t off, size_t len, const char *buf); + int (*flush)(struct rbd_ctx *ctx); + int (*discard)(struct rbd_ctx *ctx, uint64_t off, uint64_t len); + int (*get_size)(struct rbd_ctx *ctx, uint64_t *size); + int (*resize)(struct rbd_ctx *ctx, uint64_t size); + int (*clone)(struct rbd_ctx *ctx, const char *src_snapname, + const char *dst_imagename, int *order, int stripe_unit, + int stripe_count); + int (*flatten)(struct rbd_ctx *ctx); + ssize_t (*writesame)(struct rbd_ctx *ctx, uint64_t off, size_t len, + const char *buf, size_t data_len); + ssize_t (*compare_and_write)(struct rbd_ctx *ctx, uint64_t off, size_t len, + const char *cmp_buf, const char *buf); +}; + +char *pool; /* name of the pool our test image is in */ +char *iname; /* name of our test image */ +rados_t cluster; /* handle for our test cluster */ +rados_ioctx_t ioctx; /* handle for our test pool */ +#if defined(WITH_KRBD) +struct krbd_ctx *krbd; /* handle for libkrbd */ +#endif +bool skip_partial_discard; /* rbd_skip_partial_discard config value*/ + +int get_features(uint64_t* features) { + char buf[1024]; + int r = rados_conf_get(cluster, "rbd_default_features", buf, + sizeof(buf)); + if (r < 0) { + simple_err("Could not get rbd_default_features value", r); + return r; + } + + *features = strtol(buf, NULL, 0); + + if (clone_calls) { + *features |= RBD_FEATURE_LAYERING; + } + if (journal_replay) { + *features |= (RBD_FEATURE_EXCLUSIVE_LOCK | + RBD_FEATURE_JOURNALING); + } + return 0; +} + +/* + * librbd/krbd rbd_operations handlers. Given the rest of fsx.c, no + * attempt to do error handling is made in these handlers. + */ + +int +__librbd_open(const char *name, struct rbd_ctx *ctx) +{ + rbd_image_t image; + int ret; + + ceph_assert(!ctx->name && !ctx->image && + !ctx->krbd_name && ctx->krbd_fd < 0); + + ret = rbd_open(ioctx, name, &image, NULL); + if (ret < 0) { + prt("rbd_open(%s) failed\n", name); + return ret; + } + + ctx->name = strdup(name); + ctx->image = image; + ctx->krbd_name = NULL; + ctx->krbd_fd = -1; + + return 0; +} + +int +librbd_open(const char *name, struct rbd_ctx *ctx) +{ + return __librbd_open(name, ctx); +} + +int +__librbd_close(struct rbd_ctx *ctx) +{ + int ret; + + ceph_assert(ctx->name && ctx->image); + + ret = rbd_close(ctx->image); + if (ret < 0) { + prt("rbd_close(%s) failed\n", ctx->name); + return ret; + } + + free((void *)ctx->name); + + ctx->name = NULL; + ctx->image = NULL; + + return 0; +} + +int +librbd_close(struct rbd_ctx *ctx) +{ + return __librbd_close(ctx); +} + +int +librbd_verify_object_map(struct rbd_ctx *ctx) +{ + int n; + uint64_t flags; + n = rbd_get_flags(ctx->image, &flags); + if (n < 0) { + prt("rbd_get_flags() failed\n"); + return n; + } + + if ((flags & RBD_FLAG_OBJECT_MAP_INVALID) != 0) { + prt("rbd_get_flags() indicates object map is invalid\n"); + return -EINVAL; + } + return 0; +} + +ssize_t +librbd_read(struct rbd_ctx *ctx, uint64_t off, size_t len, char *buf) +{ + ssize_t n; + + n = rbd_read(ctx->image, off, len, buf); + if (n < 0) + prt("rbd_read(%llu, %zu) failed\n", off, len); + + return n; +} + +ssize_t +librbd_write(struct rbd_ctx *ctx, uint64_t off, size_t len, const char *buf) +{ + ssize_t n; + int ret; + + n = rbd_write(ctx->image, off, len, buf); + if (n < 0) { + prt("rbd_write(%llu, %zu) failed\n", off, len); + return n; + } + + ret = librbd_verify_object_map(ctx); + if (ret < 0) { + return ret; + } + return n; +} + +int +librbd_flush(struct rbd_ctx *ctx) +{ + int ret; + + ret = rbd_flush(ctx->image); + if (ret < 0) { + prt("rbd_flush failed\n"); + return ret; + } + + return librbd_verify_object_map(ctx); +} + +int +librbd_discard(struct rbd_ctx *ctx, uint64_t off, uint64_t len) +{ + int ret; + + ret = rbd_discard(ctx->image, off, len); + if (ret < 0) { + prt("rbd_discard(%llu, %llu) failed\n", off, len); + return ret; + } + + return librbd_verify_object_map(ctx); +} + +ssize_t +librbd_writesame(struct rbd_ctx *ctx, uint64_t off, size_t len, + const char *buf, size_t data_len) +{ + ssize_t n; + int ret; + + n = rbd_writesame(ctx->image, off, len, buf, data_len, 0); + if (n < 0) { + prt("rbd_writesame(%llu, %zu) failed\n", off, len); + return n; + } + + ret = librbd_verify_object_map(ctx); + if (ret < 0) { + return ret; + } + return n; +} + +ssize_t +librbd_compare_and_write(struct rbd_ctx *ctx, uint64_t off, size_t len, + const char *cmp_buf, const char *buf) +{ + ssize_t n; + int ret; + uint64_t mismatch_off = 0; + + n = rbd_compare_and_write(ctx->image, off, len, cmp_buf, buf, &mismatch_off, 0); + if (n == -EINVAL) { + return n; + } else if (n < 0) { + prt("rbd_compare_and_write mismatch(%llu, %zu, %llu) failed\n", + off, len, mismatch_off); + return n; + } + + ret = librbd_verify_object_map(ctx); + if (ret < 0) { + return ret; + } + return n; + +} + +int +librbd_get_size(struct rbd_ctx *ctx, uint64_t *size) +{ + int ret; + + ret = rbd_get_size(ctx->image, size); + if (ret < 0) { + prt("rbd_get_size failed\n"); + return ret; + } + + return 0; +} + +int +__librbd_resize(struct rbd_ctx *ctx, uint64_t size) +{ + int ret; + + ret = rbd_resize(ctx->image, size); + if (ret < 0) { + prt("rbd_resize(%llu) failed\n", size); + return ret; + } + + return librbd_verify_object_map(ctx); +} + +int +librbd_resize(struct rbd_ctx *ctx, uint64_t size) +{ + return __librbd_resize(ctx, size); +} + +int +__librbd_deep_copy(struct rbd_ctx *ctx, const char *src_snapname, + const char *dst_imagename, uint64_t features, int *order, + int stripe_unit, int stripe_count) { + int ret; + + rbd_image_options_t opts; + rbd_image_options_create(&opts); + BOOST_SCOPE_EXIT_ALL( (&opts) ) { + rbd_image_options_destroy(opts); + }; + ret = rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES, + features); + ceph_assert(ret == 0); + ret = rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_ORDER, + *order); + ceph_assert(ret == 0); + ret = rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_UNIT, + stripe_unit); + ceph_assert(ret == 0); + ret = rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_COUNT, + stripe_count); + ceph_assert(ret == 0); + + ret = rbd_snap_set(ctx->image, src_snapname); + if (ret < 0) { + prt("rbd_snap_set(%s@%s) failed\n", ctx->name, src_snapname); + return ret; + } + + ret = rbd_deep_copy(ctx->image, ioctx, dst_imagename, opts); + if (ret < 0) { + prt("rbd_deep_copy(%s@%s -> %s) failed\n", + ctx->name, src_snapname, dst_imagename); + return ret; + } + + ret = rbd_snap_set(ctx->image, ""); + if (ret < 0) { + prt("rbd_snap_set(%s@) failed\n", ctx->name); + return ret; + } + + rbd_image_t image; + ret = rbd_open(ioctx, dst_imagename, &image, nullptr); + if (ret < 0) { + prt("rbd_open(%s) failed\n", dst_imagename); + return ret; + } + + ret = rbd_snap_unprotect(image, src_snapname); + if (ret < 0) { + prt("rbd_snap_unprotect(%s@%s) failed\n", dst_imagename, + src_snapname); + return ret; + } + + ret = rbd_snap_remove(image, src_snapname); + if (ret < 0) { + prt("rbd_snap_remove(%s@%s) failed\n", dst_imagename, + src_snapname); + return ret; + } + + ret = rbd_close(image); + if (ret < 0) { + prt("rbd_close(%s) failed\n", dst_imagename); + return ret; + } + + return 0; +} + +int +__librbd_clone(struct rbd_ctx *ctx, const char *src_snapname, + const char *dst_imagename, int *order, int stripe_unit, + int stripe_count, bool krbd) +{ + int ret; + + ret = rbd_snap_create(ctx->image, src_snapname); + if (ret < 0) { + prt("rbd_snap_create(%s@%s) failed\n", ctx->name, + src_snapname); + return ret; + } + + ret = rbd_snap_protect(ctx->image, src_snapname); + if (ret < 0) { + prt("rbd_snap_protect(%s@%s) failed\n", ctx->name, + src_snapname); + return ret; + } + + uint64_t features; + ret = get_features(&features); + if (ret < 0) { + return ret; + } + + if (krbd) { + features &= ~(RBD_FEATURE_OBJECT_MAP | + RBD_FEATURE_FAST_DIFF | + RBD_FEATURE_DEEP_FLATTEN | + RBD_FEATURE_JOURNALING); + } + if (deep_copy) { + ret = __librbd_deep_copy(ctx, src_snapname, dst_imagename, features, + order, stripe_unit, stripe_count); + if (ret < 0) { + prt("deep_copy(%s@%s -> %s) failed\n", ctx->name, + src_snapname, dst_imagename); + return ret; + } + } else { + ret = rbd_clone2(ioctx, ctx->name, src_snapname, ioctx, + dst_imagename, features, order, + stripe_unit, stripe_count); + if (ret < 0) { + prt("rbd_clone2(%s@%s -> %s) failed\n", ctx->name, + src_snapname, dst_imagename); + return ret; + } + } + + return 0; +} + +int +librbd_clone(struct rbd_ctx *ctx, const char *src_snapname, + const char *dst_imagename, int *order, int stripe_unit, + int stripe_count) +{ + return __librbd_clone(ctx, src_snapname, dst_imagename, order, + stripe_unit, stripe_count, false); +} + +int +__librbd_flatten(struct rbd_ctx *ctx) +{ + int ret; + + ret = rbd_flatten(ctx->image); + if (ret < 0) { + prt("rbd_flatten failed\n"); + return ret; + } + + return librbd_verify_object_map(ctx); +} + +int +librbd_flatten(struct rbd_ctx *ctx) +{ + return __librbd_flatten(ctx); +} + +const struct rbd_operations librbd_operations = { + librbd_open, + librbd_close, + librbd_read, + librbd_write, + librbd_flush, + librbd_discard, + librbd_get_size, + librbd_resize, + librbd_clone, + librbd_flatten, + librbd_writesame, + librbd_compare_and_write, +}; + +#if defined(WITH_KRBD) +int +krbd_open(const char *name, struct rbd_ctx *ctx) +{ + char buf[1024]; + char *devnode; + int fd; + int ret; + + ret = __librbd_open(name, ctx); + if (ret < 0) + return ret; + + ret = rados_conf_get(cluster, "rbd_default_map_options", buf, + sizeof(buf)); + if (ret < 0) { + simple_err("Could not get rbd_default_map_options value", ret); + return ret; + } + + ret = krbd_map(krbd, pool, "", name, "", buf, &devnode); + if (ret < 0) { + prt("krbd_map(%s) failed\n", name); + return ret; + } + + fd = open(devnode, O_RDWR | o_direct); + if (fd < 0) { + ret = -errno; + prt("open(%s) failed\n", devnode); + return ret; + } + + ctx->krbd_name = devnode; + ctx->krbd_fd = fd; + + return 0; +} + +int +krbd_close(struct rbd_ctx *ctx) +{ + int ret; + + ceph_assert(ctx->krbd_name && ctx->krbd_fd >= 0); + + if (close(ctx->krbd_fd) < 0) { + ret = -errno; + prt("close(%s) failed\n", ctx->krbd_name); + return ret; + } + + ret = krbd_unmap(krbd, ctx->krbd_name, ""); + if (ret < 0) { + prt("krbd_unmap(%s) failed\n", ctx->krbd_name); + return ret; + } + + free((void *)ctx->krbd_name); + + ctx->krbd_name = NULL; + ctx->krbd_fd = -1; + + return __librbd_close(ctx); +} +#endif // WITH_KRBD + +#if defined(__linux__) +ssize_t +krbd_read(struct rbd_ctx *ctx, uint64_t off, size_t len, char *buf) +{ + ssize_t n; + + n = pread(ctx->krbd_fd, buf, len, off); + if (n < 0) { + n = -errno; + prt("pread(%llu, %zu) failed\n", off, len); + return n; + } + + return n; +} + +ssize_t +krbd_write(struct rbd_ctx *ctx, uint64_t off, size_t len, const char *buf) +{ + ssize_t n; + + n = pwrite(ctx->krbd_fd, buf, len, off); + if (n < 0) { + n = -errno; + prt("pwrite(%llu, %zu) failed\n", off, len); + return n; + } + + return n; +} + +int +__krbd_flush(struct rbd_ctx *ctx, bool invalidate) +{ + int ret; + + if (o_direct) + return 0; + + /* + * BLKFLSBUF will sync the filesystem on top of the device (we + * don't care about that here, since we write directly to it), + * write out any dirty buffers and invalidate the buffer cache. + * It won't do a hardware cache flush. + * + * fsync() will write out any dirty buffers and do a hardware + * cache flush (which we don't care about either, because for + * krbd it's a noop). It won't try to empty the buffer cache + * nor poke the filesystem before writing out. + * + * Given that, for our purposes, fsync is a flush, while + * BLKFLSBUF is a flush+invalidate. + */ + if (invalidate) + ret = ioctl(ctx->krbd_fd, BLKFLSBUF, NULL); + else + ret = fsync(ctx->krbd_fd); + if (ret < 0) { + ret = -errno; + prt("%s failed\n", invalidate ? "BLKFLSBUF" : "fsync"); + return ret; + } + + return 0; +} + +int +krbd_flush(struct rbd_ctx *ctx) +{ + return __krbd_flush(ctx, false); +} + +int +krbd_discard(struct rbd_ctx *ctx, uint64_t off, uint64_t len) +{ + uint64_t range[2] = { off, len }; + int ret; + + /* + * BLKZEROOUT goes straight to disk and doesn't do anything + * about dirty buffers. This means we need to flush so that + * + * write 0..3M + * discard 1..2M + * + * results in "data 0000 data" rather than "data data data" on + * disk and invalidate so that + * + * discard 1..2M + * read 0..3M + * + * returns "data 0000 data" rather than "data data data" in + * case 1..2M was cached. + * + * Note: These cache coherency issues are supposed to be fixed + * in recent kernels. + */ + ret = __krbd_flush(ctx, true); + if (ret < 0) + return ret; + + /* + * off and len must be 512-byte aligned, otherwise BLKZEROOUT + * will fail with -EINVAL. This means that -K (enable krbd + * mode) requires -h 512 or similar. + */ + if (ioctl(ctx->krbd_fd, BLKZEROOUT, &range) < 0) { + ret = -errno; + prt("BLKZEROOUT(%llu, %llu) failed\n", off, len); + return ret; + } + + return 0; +} + +int +krbd_get_size(struct rbd_ctx *ctx, uint64_t *size) +{ + uint64_t bytes; + + if (ioctl(ctx->krbd_fd, BLKGETSIZE64, &bytes) < 0) { + int ret = -errno; + prt("BLKGETSIZE64 failed\n"); + return ret; + } + + *size = bytes; + + return 0; +} + +int +krbd_resize(struct rbd_ctx *ctx, uint64_t size) +{ + int ret; + + ceph_assert(size % truncbdy == 0); + + /* + * When krbd detects a size change, it calls revalidate_disk(), + * which ends up calling invalidate_bdev(), which invalidates + * clean pages and does nothing about dirty pages beyond the + * new size. The preceding cache flush makes sure those pages + * are invalidated, which is what we need on shrink so that + * + * write 0..1M + * resize 0 + * resize 2M + * read 0..2M + * + * returns "0000 0000" rather than "data 0000". + */ + ret = __krbd_flush(ctx, false); + if (ret < 0) + return ret; + + return __librbd_resize(ctx, size); +} + +int +krbd_clone(struct rbd_ctx *ctx, const char *src_snapname, + const char *dst_imagename, int *order, int stripe_unit, + int stripe_count) +{ + int ret; + + ret = __krbd_flush(ctx, false); + if (ret < 0) + return ret; + + return __librbd_clone(ctx, src_snapname, dst_imagename, order, + stripe_unit, stripe_count, true); +} + +int +krbd_flatten(struct rbd_ctx *ctx) +{ + int ret; + + ret = __krbd_flush(ctx, false); + if (ret < 0) + return ret; + + return __librbd_flatten(ctx); +} +#endif // __linux__ + +#if defined(WITH_KRBD) +const struct rbd_operations krbd_operations = { + krbd_open, + krbd_close, + krbd_read, + krbd_write, + krbd_flush, + krbd_discard, + krbd_get_size, + krbd_resize, + krbd_clone, + krbd_flatten, + NULL, +}; +#endif // WITH_KRBD + +#if defined(__linux__) +int +nbd_open(const char *name, struct rbd_ctx *ctx) +{ + int r; + int fd; + char dev[4096]; + char *devnode; + + SubProcess process("rbd-nbd", SubProcess::KEEP, SubProcess::PIPE, + SubProcess::KEEP); + process.add_cmd_arg("map"); + process.add_cmd_arg("--timeout=600"); + std::string img; + img.append(pool); + img.append("/"); + img.append(name); + process.add_cmd_arg(img.c_str()); + + r = __librbd_open(name, ctx); + if (r < 0) + return r; + + r = process.spawn(); + if (r < 0) { + prt("nbd_open failed to run rbd-nbd error: %s\n", process.err().c_str()); + return r; + } + r = safe_read(process.get_stdout(), dev, sizeof(dev)); + if (r < 0) { + prt("nbd_open failed to get nbd device path\n"); + return r; + } + for (int i = 0; i < r; ++i) + if (dev[i] == 10 || dev[i] == 13) + dev[i] = 0; + dev[r] = 0; + r = process.join(); + if (r) { + prt("rbd-nbd failed with error: %s", process.err().c_str()); + return -EINVAL; + } + + devnode = strdup(dev); + if (!devnode) + return -ENOMEM; + + fd = open(devnode, O_RDWR | o_direct); + if (fd < 0) { + r = -errno; + prt("open(%s) failed\n", devnode); + return r; + } + + ctx->krbd_name = devnode; + ctx->krbd_fd = fd; + + return 0; +} + +int +nbd_close(struct rbd_ctx *ctx) +{ + int r; + + ceph_assert(ctx->krbd_name && ctx->krbd_fd >= 0); + + if (close(ctx->krbd_fd) < 0) { + r = -errno; + prt("close(%s) failed\n", ctx->krbd_name); + return r; + } + + SubProcess process("rbd-nbd"); + process.add_cmd_arg("unmap"); + process.add_cmd_arg(ctx->krbd_name); + + r = process.spawn(); + if (r < 0) { + prt("nbd_close failed to run rbd-nbd error: %s\n", process.err().c_str()); + return r; + } + r = process.join(); + if (r) { + prt("rbd-nbd failed with error: %d", process.err().c_str()); + return -EINVAL; + } + + free((void *)ctx->krbd_name); + + ctx->krbd_name = NULL; + ctx->krbd_fd = -1; + + return __librbd_close(ctx); +} + +int +nbd_clone(struct rbd_ctx *ctx, const char *src_snapname, + const char *dst_imagename, int *order, int stripe_unit, + int stripe_count) +{ + int ret; + + ret = __krbd_flush(ctx, false); + if (ret < 0) + return ret; + + return __librbd_clone(ctx, src_snapname, dst_imagename, order, + stripe_unit, stripe_count, false); +} + +const struct rbd_operations nbd_operations = { + nbd_open, + nbd_close, + krbd_read, + krbd_write, + krbd_flush, + krbd_discard, + krbd_get_size, + krbd_resize, + nbd_clone, + krbd_flatten, + NULL, +}; +#endif // __linux__ + +#if defined(__FreeBSD__) +int +ggate_open(const char *name, struct rbd_ctx *ctx) +{ + int r; + int fd; + char dev[4096]; + char *devnode; + + SubProcess process("rbd-ggate", SubProcess::KEEP, SubProcess::PIPE, + SubProcess::KEEP); + process.add_cmd_arg("map"); + std::string img; + img.append(pool); + img.append("/"); + img.append(name); + process.add_cmd_arg(img.c_str()); + + r = __librbd_open(name, ctx); + if (r < 0) { + return r; + } + + r = process.spawn(); + if (r < 0) { + prt("ggate_open failed to run rbd-ggate: %s\n", + process.err().c_str()); + return r; + } + r = safe_read(process.get_stdout(), dev, sizeof(dev)); + if (r < 0) { + prt("ggate_open failed to get ggate device path\n"); + return r; + } + for (int i = 0; i < r; ++i) { + if (dev[i] == '\r' || dev[i] == '\n') { + dev[i] = 0; + } + } + dev[r] = 0; + r = process.join(); + if (r) { + prt("rbd-ggate failed with error: %s", process.err().c_str()); + return -EINVAL; + } + + devnode = strdup(dev); + if (!devnode) { + return -ENOMEM; + } + + for (int i = 0; i < 100; i++) { + fd = open(devnode, O_RDWR | o_direct); + if (fd >= 0 || errno != ENOENT) { + break; + } + usleep(100000); + } + if (fd < 0) { + r = -errno; + prt("open(%s) failed\n", devnode); + return r; + } + + ctx->krbd_name = devnode; + ctx->krbd_fd = fd; + + return 0; +} + +int +ggate_close(struct rbd_ctx *ctx) +{ + int r; + + ceph_assert(ctx->krbd_name && ctx->krbd_fd >= 0); + + if (close(ctx->krbd_fd) < 0) { + r = -errno; + prt("close(%s) failed\n", ctx->krbd_name); + return r; + } + + SubProcess process("rbd-ggate"); + process.add_cmd_arg("unmap"); + process.add_cmd_arg(ctx->krbd_name); + + r = process.spawn(); + if (r < 0) { + prt("ggate_close failed to run rbd-nbd: %s\n", + process.err().c_str()); + return r; + } + r = process.join(); + if (r) { + prt("rbd-ggate failed with error: %d", process.err().c_str()); + return -EINVAL; + } + + free((void *)ctx->krbd_name); + + ctx->krbd_name = NULL; + ctx->krbd_fd = -1; + + return __librbd_close(ctx); +} + +ssize_t +ggate_read(struct rbd_ctx *ctx, uint64_t off, size_t len, char *buf) +{ + ssize_t n; + + n = pread(ctx->krbd_fd, buf, len, off); + if (n < 0) { + n = -errno; + prt("pread(%llu, %zu) failed\n", off, len); + return n; + } + + return n; +} + +ssize_t +ggate_write(struct rbd_ctx *ctx, uint64_t off, size_t len, const char *buf) +{ + ssize_t n; + + n = pwrite(ctx->krbd_fd, buf, len, off); + if (n < 0) { + n = -errno; + prt("pwrite(%llu, %zu) failed\n", off, len); + return n; + } + + return n; +} + +int +__ggate_flush(struct rbd_ctx *ctx, bool invalidate) +{ + int ret; + + if (o_direct) { + return 0; + } + + if (invalidate) { + ret = ioctl(ctx->krbd_fd, DIOCGFLUSH, NULL); + } else { + ret = fsync(ctx->krbd_fd); + } + if (ret < 0) { + ret = -errno; + prt("%s failed\n", invalidate ? "DIOCGFLUSH" : "fsync"); + return ret; + } + + return 0; +} + +int +ggate_flush(struct rbd_ctx *ctx) +{ + return __ggate_flush(ctx, false); +} + +int +ggate_discard(struct rbd_ctx *ctx, uint64_t off, uint64_t len) +{ + off_t range[2] = {static_cast<off_t>(off), static_cast<off_t>(len)}; + int ret; + + ret = __ggate_flush(ctx, true); + if (ret < 0) { + return ret; + } + + if (ioctl(ctx->krbd_fd, DIOCGDELETE, &range) < 0) { + ret = -errno; + prt("DIOCGDELETE(%llu, %llu) failed\n", off, len); + return ret; + } + + return 0; +} + +int +ggate_get_size(struct rbd_ctx *ctx, uint64_t *size) +{ + off_t bytes; + + if (ioctl(ctx->krbd_fd, DIOCGMEDIASIZE, &bytes) < 0) { + int ret = -errno; + prt("DIOCGMEDIASIZE failed\n"); + return ret; + } + + *size = bytes; + + return 0; +} + +int +ggate_resize(struct rbd_ctx *ctx, uint64_t size) +{ + int ret; + + ceph_assert(size % truncbdy == 0); + + ret = __ggate_flush(ctx, false); + if (ret < 0) { + return ret; + } + + return __librbd_resize(ctx, size); +} + +int +ggate_clone(struct rbd_ctx *ctx, const char *src_snapname, + const char *dst_imagename, int *order, int stripe_unit, + int stripe_count) +{ + int ret; + + ret = __ggate_flush(ctx, false); + if (ret < 0) { + return ret; + } + + return __librbd_clone(ctx, src_snapname, dst_imagename, order, + stripe_unit, stripe_count, false); +} + +int +ggate_flatten(struct rbd_ctx *ctx) +{ + int ret; + + ret = __ggate_flush(ctx, false); + if (ret < 0) { + return ret; + } + + return __librbd_flatten(ctx); +} + +const struct rbd_operations ggate_operations = { + ggate_open, + ggate_close, + ggate_read, + ggate_write, + ggate_flush, + ggate_discard, + ggate_get_size, + ggate_resize, + ggate_clone, + ggate_flatten, + NULL, +}; +#endif // __FreeBSD__ + +struct rbd_ctx ctx = RBD_CTX_INIT; +const struct rbd_operations *ops = &librbd_operations; + +static bool rbd_image_has_parent(struct rbd_ctx *ctx) +{ + int ret; + rbd_linked_image_spec_t parent_image; + rbd_snap_spec_t parent_snap; + + ret = rbd_get_parent(ctx->image, &parent_image, &parent_snap); + if (ret < 0 && ret != -ENOENT) { + prterrcode("rbd_get_parent_info", ret); + exit(1); + } + rbd_linked_image_spec_cleanup(&parent_image); + rbd_snap_spec_cleanup(&parent_snap); + + return !ret; +} + +/* + * fsx + */ + +void +log4(int operation, int arg0, int arg1, int arg2) +{ + struct log_entry *le; + + le = &oplog[logptr]; + le->operation = operation; + if (closeopen) + le->operation = ~ le->operation; + le->args[0] = arg0; + le->args[1] = arg1; + le->args[2] = arg2; + logptr++; + logcount++; + if (logptr >= LOGSIZE) + logptr = 0; +} + +void +logdump(void) +{ + int i, count, down; + struct log_entry *lp; + const char *falloc_type[3] = {"PAST_EOF", "EXTENDING", "INTERIOR"}; + + prt("LOG DUMP (%d total operations):\n", logcount); + if (logcount < LOGSIZE) { + i = 0; + count = logcount; + } else { + i = logptr; + count = LOGSIZE; + } + for ( ; count > 0; count--) { + int opnum; + + opnum = i+1 + (logcount/LOGSIZE)*LOGSIZE; + prt("%d(%3d mod 256): ", opnum, opnum%256); + lp = &oplog[i]; + if ((closeopen = lp->operation < 0)) + lp->operation = ~ lp->operation; + + switch (lp->operation) { + case OP_MAPREAD: + prt("MAPREAD 0x%x thru 0x%x\t(0x%x bytes)", + lp->args[0], lp->args[0] + lp->args[1] - 1, + lp->args[1]); + if (badoff >= lp->args[0] && badoff < + lp->args[0] + lp->args[1]) + prt("\t***RRRR***"); + break; + case OP_MAPWRITE: + prt("MAPWRITE 0x%x thru 0x%x\t(0x%x bytes)", + lp->args[0], lp->args[0] + lp->args[1] - 1, + lp->args[1]); + if (badoff >= lp->args[0] && badoff < + lp->args[0] + lp->args[1]) + prt("\t******WWWW"); + break; + case OP_READ: + prt("READ 0x%x thru 0x%x\t(0x%x bytes)", + lp->args[0], lp->args[0] + lp->args[1] - 1, + lp->args[1]); + if (badoff >= lp->args[0] && + badoff < lp->args[0] + lp->args[1]) + prt("\t***RRRR***"); + break; + case OP_WRITE: + prt("WRITE 0x%x thru 0x%x\t(0x%x bytes)", + lp->args[0], lp->args[0] + lp->args[1] - 1, + lp->args[1]); + if (lp->args[0] > lp->args[2]) + prt(" HOLE"); + else if (lp->args[0] + lp->args[1] > lp->args[2]) + prt(" EXTEND"); + if ((badoff >= lp->args[0] || badoff >=lp->args[2]) && + badoff < lp->args[0] + lp->args[1]) + prt("\t***WWWW"); + break; + case OP_TRUNCATE: + down = lp->args[0] < lp->args[1]; + prt("TRUNCATE %s\tfrom 0x%x to 0x%x", + down ? "DOWN" : "UP", lp->args[1], lp->args[0]); + if (badoff >= lp->args[!down] && + badoff < lp->args[!!down]) + prt("\t******WWWW"); + break; + case OP_FALLOCATE: + /* 0: offset 1: length 2: where alloced */ + prt("FALLOC 0x%x thru 0x%x\t(0x%x bytes) %s", + lp->args[0], lp->args[0] + lp->args[1], + lp->args[1], falloc_type[lp->args[2]]); + if (badoff >= lp->args[0] && + badoff < lp->args[0] + lp->args[1]) + prt("\t******FFFF"); + break; + case OP_PUNCH_HOLE: + prt("PUNCH 0x%x thru 0x%x\t(0x%x bytes)", + lp->args[0], lp->args[0] + lp->args[1] - 1, + lp->args[1]); + if (badoff >= lp->args[0] && badoff < + lp->args[0] + lp->args[1]) + prt("\t******PPPP"); + break; + case OP_WRITESAME: + prt("WRITESAME 0x%x thru 0x%x\t(0x%x bytes) data_size 0x%x", + lp->args[0], lp->args[0] + lp->args[1] - 1, + lp->args[1], lp->args[2]); + if (badoff >= lp->args[0] && + badoff < lp->args[0] + lp->args[1]) + prt("\t***WSWSWSWS"); + break; + case OP_COMPARE_AND_WRITE: + prt("COMPARE_AND_WRITE 0x%x thru 0x%x\t(0x%x bytes)", + lp->args[0], lp->args[0] + lp->args[1] - 1, + lp->args[1]); + if (lp->args[0] > lp->args[2]) + prt(" HOLE"); + else if (lp->args[0] + lp->args[1] > lp->args[2]) + prt(" EXTEND"); + if ((badoff >= lp->args[0] || badoff >=lp->args[2]) && + badoff < lp->args[0] + lp->args[1]) + prt("\t***WWWW"); + break; + case OP_CLONE: + prt("CLONE"); + break; + case OP_FLATTEN: + prt("FLATTEN"); + break; + case OP_SKIPPED: + prt("SKIPPED (no operation)"); + break; + default: + prt("BOGUS LOG ENTRY (operation code = %d)!", + lp->operation); + } + if (closeopen) + prt("\n\t\tCLOSE/OPEN"); + prt("\n"); + i++; + if (i == LOGSIZE) + i = 0; + } +} + +void +save_buffer(char *buffer, off_t bufferlength, int fd) +{ + off_t ret; + ssize_t byteswritten; + + if (fd <= 0 || bufferlength == 0) + return; + + if (bufferlength > SSIZE_MAX) { + prt("fsx flaw: overflow in save_buffer\n"); + exit(67); + } + + ret = lseek(fd, (off_t)0, SEEK_SET); + if (ret == (off_t)-1) + prterr("save_buffer: lseek 0"); + + byteswritten = write(fd, buffer, (size_t)bufferlength); + if (byteswritten != bufferlength) { + if (byteswritten == -1) + prterr("save_buffer write"); + else + warn("save_buffer: short write, 0x%x bytes instead of 0x%llx\n", + (unsigned)byteswritten, + (unsigned long long)bufferlength); + } +} + + +void +report_failure(int status) +{ + logdump(); + + if (fsxgoodfd) { + if (good_buf) { + save_buffer(good_buf, file_size, fsxgoodfd); + prt("Correct content saved for comparison\n"); + prt("(maybe hexdump \"%s\" vs \"%s.fsxgood\")\n", + iname, iname); + } + close(fsxgoodfd); + } + sleep(3); // so the log can flush to disk. KLUDGEY! + exit(status); +} + +#define short_at(cp) ((unsigned short)((*((unsigned char *)(cp)) << 8) | \ + *(((unsigned char *)(cp)) + 1))) + +int +fsxcmp(char *good_buf, char *temp_buf, unsigned size) +{ + if (!skip_partial_discard) { + return memcmp(good_buf, temp_buf, size); + } + + for (unsigned i = 0; i < size; i++) { + if (good_buf[i] != temp_buf[i] && good_buf[i] != 0) { + return good_buf[i] - temp_buf[i]; + } + } + return 0; +} + +void +check_buffers(char *good_buf, char *temp_buf, unsigned offset, unsigned size) +{ + if (fsxcmp(good_buf + offset, temp_buf, size) != 0) { + unsigned i = 0; + unsigned n = 0; + + prt("READ BAD DATA: offset = 0x%x, size = 0x%x, fname = %s\n", + offset, size, iname); + prt("OFFSET\tGOOD\tBAD\tRANGE\n"); + while (size > 0) { + unsigned char c = good_buf[offset]; + unsigned char t = temp_buf[i]; + if (c != t) { + if (n < 16) { + unsigned bad = short_at(&temp_buf[i]); + prt("0x%5x\t0x%04x\t0x%04x", offset, + short_at(&good_buf[offset]), bad); + unsigned op = temp_buf[(offset & 1) ? i+1 : i]; + prt("\t0x%5x\n", n); + if (op) + prt("operation# (mod 256) for " + "the bad data may be %u\n", + ((unsigned)op & 0xff)); + else + prt("operation# (mod 256) for " + "the bad data unknown, check" + " HOLE and EXTEND ops\n"); + } + n++; + badoff = offset; + } + offset++; + i++; + size--; + } + report_failure(110); + } +} + + +void +check_size(void) +{ + uint64_t size; + int ret; + + ret = ops->get_size(&ctx, &size); + if (ret < 0) + prterrcode("check_size: ops->get_size", ret); + + if ((uint64_t)file_size != size) { + prt("Size error: expected 0x%llx stat 0x%llx\n", + (unsigned long long)file_size, + (unsigned long long)size); + report_failure(120); + } +} + +#define TRUNC_HACK_SIZE (200ULL << 9) /* 512-byte aligned for krbd */ + +void +check_trunc_hack(void) +{ + uint64_t size; + int ret; + + ret = ops->resize(&ctx, 0ULL); + if (ret < 0) + prterrcode("check_trunc_hack: ops->resize pre", ret); + + ret = ops->resize(&ctx, TRUNC_HACK_SIZE); + if (ret < 0) + prterrcode("check_trunc_hack: ops->resize actual", ret); + + ret = ops->get_size(&ctx, &size); + if (ret < 0) + prterrcode("check_trunc_hack: ops->get_size", ret); + + if (size != TRUNC_HACK_SIZE) { + prt("no extend on truncate! not posix!\n"); + exit(130); + } + + ret = ops->resize(&ctx, 0ULL); + if (ret < 0) + prterrcode("check_trunc_hack: ops->resize post", ret); +} + +int +create_image() +{ + int r; + int order = 0; + char buf[32]; + char client_name[256]; + + sprintf(client_name, "client.%s", client_id); + + r = rados_create2(&cluster, cluster_name, client_name, 0); + if (r < 0) { + simple_err("Could not create cluster handle", r); + return r; + } + rados_conf_parse_env(cluster, NULL); + r = rados_conf_read_file(cluster, NULL); + if (r < 0) { + simple_err("Error reading ceph config file", r); + goto failed_shutdown; + } + r = rados_connect(cluster); + if (r < 0) { + simple_err("Error connecting to cluster", r); + goto failed_shutdown; + } +#if defined(WITH_KRBD) + r = krbd_create_from_context(rados_cct(cluster), 0, &krbd); + if (r < 0) { + simple_err("Could not create libkrbd handle", r); + goto failed_shutdown; + } +#endif + + r = rados_pool_create(cluster, pool); + if (r < 0 && r != -EEXIST) { + simple_err("Error creating pool", r); + goto failed_krbd; + } + r = rados_ioctx_create(cluster, pool, &ioctx); + if (r < 0) { + simple_err("Error creating ioctx", r); + goto failed_krbd; + } + rados_application_enable(ioctx, "rbd", 1); + + if (clone_calls || journal_replay) { + uint64_t features; + r = get_features(&features); + if (r < 0) { + goto failed_open; + } + + r = rbd_create2(ioctx, iname, file_size, features, &order); + } else { + r = rbd_create(ioctx, iname, file_size, &order); + } + if (r < 0) { + simple_err("Error creating image", r); + goto failed_open; + } + + if (journal_replay) { + r = register_journal(ioctx, iname); + if (r < 0) { + goto failed_open; + } + } + + r = rados_conf_get(cluster, "rbd_skip_partial_discard", buf, + sizeof(buf)); + if (r < 0) { + simple_err("Could not get rbd_skip_partial_discard value", r); + goto failed_open; + } + skip_partial_discard = (strcmp(buf, "true") == 0); + + return 0; + + failed_open: + rados_ioctx_destroy(ioctx); + failed_krbd: +#if defined(WITH_KRBD) + krbd_destroy(krbd); +#endif + failed_shutdown: + rados_shutdown(cluster); + return r; +} + +void +doflush(unsigned offset, unsigned size) +{ + int ret; + + if (o_direct) + return; + + ret = ops->flush(&ctx); + if (ret < 0) + prterrcode("doflush: ops->flush", ret); +} + +void +doread(unsigned offset, unsigned size) +{ + int ret; + + offset -= offset % readbdy; + if (o_direct) + size -= size % readbdy; + if (size == 0) { + if (!quiet && testcalls > simulatedopcount && !o_direct) + prt("skipping zero size read\n"); + log4(OP_SKIPPED, OP_READ, offset, size); + return; + } + if (size + offset > file_size) { + if (!quiet && testcalls > simulatedopcount) + prt("skipping seek/read past end of file\n"); + log4(OP_SKIPPED, OP_READ, offset, size); + return; + } + + log4(OP_READ, offset, size, 0); + + if (testcalls <= simulatedopcount) + return; + + if (!quiet && + ((progressinterval && testcalls % progressinterval == 0) || + (debug && + (monitorstart == -1 || + (static_cast<long>(offset + size) > monitorstart && + (monitorend == -1 || + static_cast<long>(offset) <= monitorend)))))) + prt("%lu read\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls, + offset, offset + size - 1, size); + + ret = ops->read(&ctx, offset, size, temp_buf); + if (ret != (int)size) { + if (ret < 0) + prterrcode("doread: ops->read", ret); + else + prt("short read: 0x%x bytes instead of 0x%x\n", + ret, size); + report_failure(141); + } + + check_buffers(good_buf, temp_buf, offset, size); +} + + +void +check_eofpage(char *s, unsigned offset, char *p, int size) +{ + unsigned long last_page, should_be_zero; + + if (offset + size <= (file_size & ~page_mask)) + return; + /* + * we landed in the last page of the file + * test to make sure the VM system provided 0's + * beyond the true end of the file mapping + * (as required by mmap def in 1996 posix 1003.1) + */ + last_page = ((unsigned long)p + (offset & page_mask) + size) & ~page_mask; + + for (should_be_zero = last_page + (file_size & page_mask); + should_be_zero < last_page + page_size; + should_be_zero++) + if (*(char *)should_be_zero) { + prt("Mapped %s: non-zero data past EOF (0x%llx) page offset 0x%x is 0x%04x\n", + s, file_size - 1, should_be_zero & page_mask, + short_at(should_be_zero)); + report_failure(205); + } +} + + +void +gendata(char *original_buf, char *good_buf, unsigned offset, unsigned size) +{ + while (size--) { + good_buf[offset] = testcalls % 256; + if (offset % 2) + good_buf[offset] += original_buf[offset]; + offset++; + } +} + + +void +dowrite(unsigned offset, unsigned size) +{ + ssize_t ret; + off_t newsize; + + offset -= offset % writebdy; + if (o_direct) + size -= size % writebdy; + if (size == 0) { + if (!quiet && testcalls > simulatedopcount && !o_direct) + prt("skipping zero size write\n"); + log4(OP_SKIPPED, OP_WRITE, offset, size); + return; + } + + log4(OP_WRITE, offset, size, file_size); + + gendata(original_buf, good_buf, offset, size); + if (file_size < offset + size) { + newsize = ceil(((double)offset + size) / truncbdy) * truncbdy; + if (file_size < newsize) + memset(good_buf + file_size, '\0', newsize - file_size); + file_size = newsize; + if (lite) { + warn("Lite file size bug in fsx!"); + report_failure(149); + } + ret = ops->resize(&ctx, newsize); + if (ret < 0) { + prterrcode("dowrite: ops->resize", ret); + report_failure(150); + } + } + + if (testcalls <= simulatedopcount) + return; + + if (!quiet && + ((progressinterval && testcalls % progressinterval == 0) || + (debug && + (monitorstart == -1 || + (static_cast<long>(offset + size) > monitorstart && + (monitorend == -1 || + static_cast<long>(offset) <= monitorend)))))) + prt("%lu write\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls, + offset, offset + size - 1, size); + + ret = ops->write(&ctx, offset, size, good_buf + offset); + if (ret != (ssize_t)size) { + if (ret < 0) + prterrcode("dowrite: ops->write", ret); + else + prt("short write: 0x%x bytes instead of 0x%x\n", + ret, size); + report_failure(151); + } + + if (flush_enabled) + doflush(offset, size); +} + + +void +dotruncate(unsigned size) +{ + int oldsize = file_size; + int ret; + + size -= size % truncbdy; + if (size > biggest) { + biggest = size; + if (!quiet && testcalls > simulatedopcount) + prt("truncating to largest ever: 0x%x\n", size); + } + + log4(OP_TRUNCATE, size, (unsigned)file_size, 0); + + if (size > file_size) + memset(good_buf + file_size, '\0', size - file_size); + else if (size < file_size) + memset(good_buf + size, '\0', file_size - size); + file_size = size; + + if (testcalls <= simulatedopcount) + return; + + if ((progressinterval && testcalls % progressinterval == 0) || + (debug && (monitorstart == -1 || monitorend == -1 || + (long)size <= monitorend))) + prt("%lu trunc\tfrom 0x%x to 0x%x\n", testcalls, oldsize, size); + + ret = ops->resize(&ctx, size); + if (ret < 0) { + prterrcode("dotruncate: ops->resize", ret); + report_failure(160); + } +} + +void +do_punch_hole(unsigned offset, unsigned length) +{ + unsigned end_offset; + int max_offset = 0; + int max_len = 0; + int ret; + + offset -= offset % holebdy; + length -= length % holebdy; + if (length == 0) { + if (!quiet && testcalls > simulatedopcount) + prt("skipping zero length punch hole\n"); + log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, length); + return; + } + + if (file_size <= (loff_t)offset) { + if (!quiet && testcalls > simulatedopcount) + prt("skipping hole punch off the end of the file\n"); + log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, length); + return; + } + + end_offset = offset + length; + + log4(OP_PUNCH_HOLE, offset, length, 0); + + if (testcalls <= simulatedopcount) + return; + + if ((progressinterval && testcalls % progressinterval == 0) || + (debug && (monitorstart == -1 || monitorend == -1 || + (long)end_offset <= monitorend))) { + prt("%lu punch\tfrom 0x%x to 0x%x, (0x%x bytes)\n", testcalls, + offset, offset+length, length); + } + + ret = ops->discard(&ctx, (unsigned long long)offset, + (unsigned long long)length); + if (ret < 0) { + prterrcode("do_punch_hole: ops->discard", ret); + report_failure(161); + } + + max_offset = offset < file_size ? offset : file_size; + max_len = max_offset + length <= file_size ? length : + file_size - max_offset; + memset(good_buf + max_offset, '\0', max_len); +} + +unsigned get_data_size(unsigned size) +{ + unsigned i; + unsigned hint; + unsigned max = sqrt((double)size) + 1; + unsigned good = 1; + unsigned curr = good; + + hint = get_random() % max; + + for (i = 1; i < max && curr < hint; i++) { + if (size % i == 0) { + good = curr; + curr = i; + } + } + + if (curr == hint) + good = curr; + + return good; +} + +void +dowritesame(unsigned offset, unsigned size) +{ + ssize_t ret; + off_t newsize; + unsigned buf_off; + unsigned data_size; + int n; + + offset -= offset % writebdy; + if (o_direct) + size -= size % writebdy; + if (size == 0) { + if (!quiet && testcalls > simulatedopcount && !o_direct) + prt("skipping zero size writesame\n"); + log4(OP_SKIPPED, OP_WRITESAME, offset, size); + return; + } + + data_size = get_data_size(size); + + log4(OP_WRITESAME, offset, size, data_size); + + gendata(original_buf, good_buf, offset, data_size); + if (file_size < offset + size) { + newsize = ceil(((double)offset + size) / truncbdy) * truncbdy; + if (file_size < newsize) + memset(good_buf + file_size, '\0', newsize - file_size); + file_size = newsize; + if (lite) { + warn("Lite file size bug in fsx!"); + report_failure(162); + } + ret = ops->resize(&ctx, newsize); + if (ret < 0) { + prterrcode("dowritesame: ops->resize", ret); + report_failure(163); + } + } + + for (n = size / data_size, buf_off = data_size; n > 1; n--) { + memcpy(good_buf + offset + buf_off, good_buf + offset, data_size); + buf_off += data_size; + } + + if (testcalls <= simulatedopcount) + return; + + if (!quiet && + ((progressinterval && testcalls % progressinterval == 0) || + (debug && + (monitorstart == -1 || + (static_cast<long>(offset + size) > monitorstart && + (monitorend == -1 || + static_cast<long>(offset) <= monitorend)))))) + prt("%lu writesame\t0x%x thru\t0x%x\tdata_size\t0x%x(0x%x bytes)\n", testcalls, + offset, offset + size - 1, data_size, size); + + ret = ops->writesame(&ctx, offset, size, good_buf + offset, data_size); + if (ret != (ssize_t)size) { + if (ret < 0) + prterrcode("dowritesame: ops->writesame", ret); + else + prt("short writesame: 0x%x bytes instead of 0x%x\n", + ret, size); + report_failure(164); + } + + if (flush_enabled) + doflush(offset, size); +} + +void +docompareandwrite(unsigned offset, unsigned size) +{ + int ret; + + if (skip_partial_discard) { + if (!quiet && testcalls > simulatedopcount) + prt("compare and write disabled\n"); + log4(OP_SKIPPED, OP_COMPARE_AND_WRITE, offset, size); + return; + } + + offset -= offset % writebdy; + if (o_direct) + size -= size % writebdy; + + if (size == 0) { + if (!quiet && testcalls > simulatedopcount && !o_direct) + prt("skipping zero size read\n"); + log4(OP_SKIPPED, OP_READ, offset, size); + return; + } + + if (size + offset > file_size) { + if (!quiet && testcalls > simulatedopcount) + prt("skipping seek/compare past end of file\n"); + log4(OP_SKIPPED, OP_COMPARE_AND_WRITE, offset, size); + return; + } + + memcpy(temp_buf + offset, good_buf + offset, size); + gendata(original_buf, good_buf, offset, size); + log4(OP_COMPARE_AND_WRITE, offset, size, 0); + + if (testcalls <= simulatedopcount) + return; + + if (!quiet && + ((progressinterval && testcalls % progressinterval == 0) || + (debug && + (monitorstart == -1 || + (static_cast<long>(offset + size) > monitorstart && + (monitorend == -1 || + static_cast<long>(offset) <= monitorend)))))) + prt("%lu compareandwrite\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls, + offset, offset + size - 1, size); + + ret = ops->compare_and_write(&ctx, offset, size, temp_buf + offset, + good_buf + offset); + if (ret != (ssize_t)size) { + if (ret == -EINVAL) { + memcpy(good_buf + offset, temp_buf + offset, size); + return; + } + if (ret < 0) + prterrcode("docompareandwrite: ops->compare_and_write", ret); + else + prt("short write: 0x%x bytes instead of 0x%x\n", ret, size); + report_failure(151); + return; + } + + if (flush_enabled) + doflush(offset, size); +} + +void clone_filename(char *buf, size_t len, int clones) +{ +#if __GNUC__ && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" +#endif + snprintf(buf, len, "%s/fsx-%s-parent%d", + dirpath, iname, clones); +#if __GNUC__ && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#endif +} + +void clone_imagename(char *buf, size_t len, int clones) +{ + if (clones > 0) + snprintf(buf, len, "%s-clone%d", iname, clones); + else + strncpy(buf, iname, len); + buf[len - 1] = '\0'; +} + +void replay_imagename(char *buf, size_t len, int clones) +{ + clone_imagename(buf, len, clones); + strncat(buf, "-replay", len - strlen(buf)); + buf[len - 1] = '\0'; +} + +void check_clone(int clonenum, bool replay_image); + +void +do_clone() +{ + char filename[1024]; + char imagename[1024]; + char lastimagename[1024]; + int ret, fd; + int order = 0, stripe_unit = 0, stripe_count = 0; + uint64_t newsize = file_size; + + log4(OP_CLONE, 0, 0, 0); + ++num_clones; + + if (randomize_striping) { + order = 18 + get_random() % 8; + stripe_unit = 1ull << (order - 1 - (get_random() % 8)); + stripe_count = 2 + get_random() % 14; + } + + prt("%lu clone\t%d order %d su %d sc %d\n", testcalls, num_clones, + order, stripe_unit, stripe_count); + + clone_imagename(imagename, sizeof(imagename), num_clones); + clone_imagename(lastimagename, sizeof(lastimagename), + num_clones - 1); + ceph_assert(strcmp(lastimagename, ctx.name) == 0); + + ret = ops->clone(&ctx, "snap", imagename, &order, stripe_unit, + stripe_count); + if (ret < 0) { + prterrcode("do_clone: ops->clone", ret); + exit(165); + } + + if (randomize_parent_overlap && rbd_image_has_parent(&ctx)) { + int rand = get_random() % 16 + 1; // [1..16] + + if (rand < 13) { + uint64_t overlap; + + ret = rbd_get_overlap(ctx.image, &overlap); + if (ret < 0) { + prterrcode("do_clone: rbd_get_overlap", ret); + exit(1); + } + + if (rand < 10) { // 9/16 + newsize = overlap * ((double)rand / 10); + newsize -= newsize % truncbdy; + } else { // 3/16 + newsize = 0; + } + + ceph_assert(newsize != (uint64_t)file_size); + prt("truncating image %s from 0x%llx (overlap 0x%llx) to 0x%llx\n", + ctx.name, file_size, overlap, newsize); + + ret = ops->resize(&ctx, newsize); + if (ret < 0) { + prterrcode("do_clone: ops->resize", ret); + exit(1); + } + } else if (rand < 15) { // 2/16 + prt("flattening image %s\n", ctx.name); + + ret = ops->flatten(&ctx); + if (ret < 0) { + prterrcode("do_clone: ops->flatten", ret); + exit(1); + } + } else { // 2/16 + prt("leaving image %s intact\n", ctx.name); + } + } + + clone_filename(filename, sizeof(filename), num_clones); + if ((fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) { + simple_err("do_clone: open", -errno); + exit(162); + } + save_buffer(good_buf, newsize, fd); + if ((ret = close(fd)) < 0) { + simple_err("do_clone: close", -errno); + exit(163); + } + + /* + * Close parent. + */ + if ((ret = ops->close(&ctx)) < 0) { + prterrcode("do_clone: ops->close", ret); + exit(174); + } + + if (journal_replay) { + ret = finalize_journal(ioctx, lastimagename, num_clones - 1, + order, stripe_unit, stripe_count); + if (ret < 0) { + exit(EXIT_FAILURE); + } + + ret = register_journal(ioctx, imagename); + if (ret < 0) { + exit(EXIT_FAILURE); + } + } + + /* + * Open freshly made clone. + */ + if ((ret = ops->open(imagename, &ctx)) < 0) { + prterrcode("do_clone: ops->open", ret); + exit(166); + } + + if (num_clones > 1) { + if (journal_replay) { + check_clone(num_clones - 2, true); + } + check_clone(num_clones - 2, false); + } +} + +void +check_clone(int clonenum, bool replay_image) +{ + char filename[128]; + char imagename[128]; + int ret, fd; + struct rbd_ctx cur_ctx = RBD_CTX_INIT; + struct stat file_info; + char *good_buf, *temp_buf; + + if (replay_image) { + replay_imagename(imagename, sizeof(imagename), clonenum); + } else { + clone_imagename(imagename, sizeof(imagename), clonenum); + } + + if ((ret = ops->open(imagename, &cur_ctx)) < 0) { + prterrcode("check_clone: ops->open", ret); + exit(167); + } + + clone_filename(filename, sizeof(filename), clonenum + 1); + if ((fd = open(filename, O_RDONLY)) < 0) { + simple_err("check_clone: open", -errno); + exit(168); + } + + prt("checking clone #%d, image %s against file %s\n", + clonenum, imagename, filename); + if ((ret = fstat(fd, &file_info)) < 0) { + simple_err("check_clone: fstat", -errno); + exit(169); + } + + good_buf = NULL; + ret = posix_memalign((void **)&good_buf, + std::max(writebdy, (int)sizeof(void *)), + file_info.st_size); + if (ret > 0) { + prterrcode("check_clone: posix_memalign(good_buf)", -ret); + exit(96); + } + + temp_buf = NULL; + ret = posix_memalign((void **)&temp_buf, + std::max(readbdy, (int)sizeof(void *)), + file_info.st_size); + if (ret > 0) { + prterrcode("check_clone: posix_memalign(temp_buf)", -ret); + exit(97); + } + + if ((ret = pread(fd, good_buf, file_info.st_size, 0)) < 0) { + simple_err("check_clone: pread", -errno); + exit(170); + } + if ((ret = ops->read(&cur_ctx, 0, file_info.st_size, temp_buf)) < 0) { + prterrcode("check_clone: ops->read", ret); + exit(171); + } + close(fd); + if ((ret = ops->close(&cur_ctx)) < 0) { + prterrcode("check_clone: ops->close", ret); + exit(174); + } + check_buffers(good_buf, temp_buf, 0, file_info.st_size); + + if (!replay_image) { + unlink(filename); + } + + free(good_buf); + free(temp_buf); +} + +void +writefileimage() +{ + ssize_t ret; + + ret = ops->write(&ctx, 0, file_size, good_buf); + if (ret != file_size) { + if (ret < 0) + prterrcode("writefileimage: ops->write", ret); + else + prt("short write: 0x%x bytes instead of 0x%llx\n", + ret, (unsigned long long)file_size); + report_failure(172); + } + + if (!lite) { + ret = ops->resize(&ctx, file_size); + if (ret < 0) { + prterrcode("writefileimage: ops->resize", ret); + report_failure(173); + } + } +} + +void +do_flatten() +{ + int ret; + + if (!rbd_image_has_parent(&ctx)) { + log4(OP_SKIPPED, OP_FLATTEN, 0, 0); + return; + } + log4(OP_FLATTEN, 0, 0, 0); + prt("%lu flatten\n", testcalls); + + ret = ops->flatten(&ctx); + if (ret < 0) { + prterrcode("writefileimage: ops->flatten", ret); + exit(177); + } +} + +void +docloseopen(void) +{ + char *name; + int ret; + + if (testcalls <= simulatedopcount) + return; + + name = strdup(ctx.name); + + if (debug) + prt("%lu close/open\n", testcalls); + + ret = ops->close(&ctx); + if (ret < 0) { + prterrcode("docloseopen: ops->close", ret); + report_failure(180); + } + + ret = ops->open(name, &ctx); + if (ret < 0) { + prterrcode("docloseopen: ops->open", ret); + report_failure(181); + } + + free(name); +} + +#define TRIM_OFF_LEN(off, len, size) \ +do { \ + if (size) \ + (off) %= (size); \ + else \ + (off) = 0; \ + if ((unsigned)(off) + (unsigned)(len) > (unsigned)(size)) \ + (len) = (size) - (off); \ +} while (0) + +void +test(void) +{ + unsigned long offset; + unsigned long size = maxoplen; + unsigned long rv = get_random(); + unsigned long op; + + if (simulatedopcount > 0 && testcalls == simulatedopcount) + writefileimage(); + + testcalls++; + + if (closeprob) + closeopen = (rv >> 3) < (1u << 28) / (unsigned)closeprob; + + if (debugstart > 0 && testcalls >= debugstart) + debug = 1; + + if (!quiet && testcalls < simulatedopcount && testcalls % 100000 == 0) + prt("%lu...\n", testcalls); + + offset = get_random(); + if (randomoplen) + size = get_random() % (maxoplen + 1); + + /* calculate appropriate op to run */ + if (lite) + op = rv % OP_MAX_LITE; + else + op = rv % OP_MAX_FULL; + + switch (op) { + case OP_MAPREAD: + if (!mapped_reads) + op = OP_READ; + break; + case OP_MAPWRITE: + if (!mapped_writes) + op = OP_WRITE; + break; + case OP_FALLOCATE: + if (!fallocate_calls) { + log4(OP_SKIPPED, OP_FALLOCATE, offset, size); + goto out; + } + break; + case OP_PUNCH_HOLE: + if (!punch_hole_calls) { + log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, size); + goto out; + } + break; + case OP_CLONE: + /* clone, 8% chance */ + if (!clone_calls || file_size == 0 || get_random() % 100 >= 8) { + log4(OP_SKIPPED, OP_CLONE, 0, 0); + goto out; + } + break; + case OP_FLATTEN: + /* flatten four times as rarely as clone, 2% chance */ + if (get_random() % 100 >= 2) { + log4(OP_SKIPPED, OP_FLATTEN, 0, 0); + goto out; + } + break; + case OP_WRITESAME: + /* writesame not implemented */ + if (!ops->writesame) { + log4(OP_SKIPPED, OP_WRITESAME, offset, size); + goto out; + } + break; + case OP_COMPARE_AND_WRITE: + /* compare_and_write not implemented */ + if (!ops->compare_and_write) { + log4(OP_SKIPPED, OP_COMPARE_AND_WRITE, offset, size); + goto out; + } + break; + } + + switch (op) { + case OP_READ: + TRIM_OFF_LEN(offset, size, file_size); + doread(offset, size); + break; + + case OP_WRITE: + TRIM_OFF_LEN(offset, size, maxfilelen); + dowrite(offset, size); + break; + + case OP_MAPREAD: + TRIM_OFF_LEN(offset, size, file_size); + exit(183); + break; + + case OP_MAPWRITE: + TRIM_OFF_LEN(offset, size, maxfilelen); + exit(182); + break; + + case OP_TRUNCATE: + if (!style) + size = get_random() % maxfilelen; + dotruncate(size); + break; + + case OP_PUNCH_HOLE: + TRIM_OFF_LEN(offset, size, file_size); + do_punch_hole(offset, size); + break; + + case OP_WRITESAME: + TRIM_OFF_LEN(offset, size, maxfilelen); + dowritesame(offset, size); + break; + case OP_COMPARE_AND_WRITE: + TRIM_OFF_LEN(offset, size, file_size); + docompareandwrite(offset, size); + break; + + case OP_CLONE: + do_clone(); + break; + + case OP_FLATTEN: + do_flatten(); + break; + + default: + prterr("test: unknown operation"); + report_failure(42); + break; + } + +out: + if (sizechecks && testcalls > simulatedopcount) + check_size(); + if (closeopen) + docloseopen(); +} + + +void +cleanup(int sig) +{ + if (sig) + prt("signal %d\n", sig); + prt("testcalls = %lu\n", testcalls); + exit(sig); +} + + +void +usage(void) +{ + fprintf(stdout, "usage: %s", + "fsx [-dfjknqxyACFHKLORUWZ] [-b opnum] [-c Prob] [-h holebdy] [-l flen] [-m start:end] [-o oplen] [-p progressinterval] [-r readbdy] [-s style] [-t truncbdy] [-w writebdy] [-D startingop] [-N numops] [-P dirpath] [-S seed] pname iname\n\ + -b opnum: beginning operation number (default 1)\n\ + -c P: 1 in P chance of file close+open at each op (default infinity)\n\ + -d: debug output for all operations\n\ + -f: flush and invalidate cache after I/O\n\ + -g: deep copy instead of clone\n\ + -h holebdy: 4096 would make discards page aligned (default 1)\n\ + -j: journal replay stress test\n\ + -k: keep data on success (default 0)\n\ + -l flen: the upper bound on file size (default 262144)\n\ + -m startop:endop: monitor (print debug output) specified byte range (default 0:infinity)\n\ + -n: no verifications of file size\n\ + -o oplen: the upper bound on operation size (default 65536)\n\ + -p progressinterval: debug output at specified operation interval\n\ + -q: quieter operation\n\ + -r readbdy: 4096 would make reads page aligned (default 1)\n\ + -s style: 1 gives smaller truncates (default 0)\n\ + -t truncbdy: 4096 would make truncates page aligned (default 1)\n\ + -w writebdy: 4096 would make writes page aligned (default 1)\n\ + -x: preallocate file space before starting, XFS only (default 0)\n\ + -y: synchronize changes to a file\n" + +" -C: do not use clone calls\n\ + -D startingop: debug output starting at specified operation\n" +#ifdef FALLOCATE +" -F: Do not use fallocate (preallocation) calls\n" +#endif +#if defined(__FreeBSD__) +" -G: enable rbd-ggate mode (use -L, -r and -w too)\n" +#endif +" -H: do not use punch hole calls\n" +#if defined(WITH_KRBD) +" -K: enable krbd mode (use -t and -h too)\n" +#endif +#if defined(__linux__) +" -M: enable rbd-nbd mode (use -t and -h too)\n" +#endif +" -L: fsxLite - no file creations & no file size changes\n\ + -N numops: total # operations to do (default infinity)\n\ + -O: use oplen (see -o flag) for every op (default random)\n\ + -P dirpath: save .fsxlog and .fsxgood files in dirpath (default ./)\n\ + -R: read() system calls only (mapped reads disabled)\n\ + -S seed: for random # generator (default 1) 0 gets timestamp\n\ + -U: disable randomized striping\n\ + -W: mapped write operations DISabled\n\ + -Z: O_DIRECT (use -R, -W, -r and -w too)\n\ + poolname: this is REQUIRED (no default)\n\ + imagename: this is REQUIRED (no default)\n"); + exit(89); +} + + +int +getnum(char *s, char **e) +{ + int ret; + + *e = (char *) 0; + ret = strtol(s, e, 0); + if (*e) + switch (**e) { + case 'b': + case 'B': + ret *= 512; + *e = *e + 1; + break; + case 'k': + case 'K': + ret *= 1024; + *e = *e + 1; + break; + case 'm': + case 'M': + ret *= 1024*1024; + *e = *e + 1; + break; + case 'w': + case 'W': + ret *= 4; + *e = *e + 1; + break; + } + return (ret); +} + +void +test_fallocate() +{ +#ifdef FALLOCATE + if (!lite && fallocate_calls) { + if (fallocate(fd, 0, 0, 1) && errno == EOPNOTSUPP) { + if(!quiet) + warn("main: filesystem does not support fallocate, disabling\n"); + fallocate_calls = 0; + } else { + ftruncate(fd, 0); + } + } +#else /* ! FALLOCATE */ + fallocate_calls = 0; +#endif + +} + +void remove_image(rados_ioctx_t ioctx, char *imagename, bool remove_snap, + bool unregister) { + rbd_image_t image; + char errmsg[128]; + int ret; + + if ((ret = rbd_open(ioctx, imagename, &image, NULL)) < 0) { + sprintf(errmsg, "rbd_open %s", imagename); + prterrcode(errmsg, ret); + report_failure(101); + } + if (remove_snap) { + if ((ret = rbd_snap_unprotect(image, "snap")) < 0) { + sprintf(errmsg, "rbd_snap_unprotect %s@snap", + imagename); + prterrcode(errmsg, ret); + report_failure(102); + } + if ((ret = rbd_snap_remove(image, "snap")) < 0) { + sprintf(errmsg, "rbd_snap_remove %s@snap", + imagename); + prterrcode(errmsg, ret); + report_failure(103); + } + } + if ((ret = rbd_close(image)) < 0) { + sprintf(errmsg, "rbd_close %s", imagename); + prterrcode(errmsg, ret); + report_failure(104); + } + + if (unregister && + (ret = unregister_journal(ioctx, imagename)) < 0) { + report_failure(105); + } + + if ((ret = rbd_remove(ioctx, imagename)) < 0) { + sprintf(errmsg, "rbd_remove %s", imagename); + prterrcode(errmsg, ret); + report_failure(106); + } +} + +int +main(int argc, char **argv) +{ + enum { + LONG_OPT_CLUSTER = 1000, + LONG_OPT_ID = 1001 + }; + + int i, style, ch, ret; + char *endp; + char goodfile[1024]; + char logfile[1024]; + + const char* optstring = "b:c:dfgh:jkl:m:no:p:qr:s:t:w:xyCD:FGHKMLN:OP:RS:UWZ"; + const struct option longopts[] = { + {"cluster", 1, NULL, LONG_OPT_CLUSTER}, + {"id", 1, NULL, LONG_OPT_ID}}; + + goodfile[0] = 0; + logfile[0] = 0; + + page_size = getpagesize(); + page_mask = page_size - 1; + mmap_mask = page_mask; + + setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */ + + while ((ch = getopt_long(argc, argv, optstring, longopts, NULL)) != EOF) { + switch (ch) { + case LONG_OPT_CLUSTER: + cluster_name = optarg; + break; + case LONG_OPT_ID: + client_id = optarg; + break; + case 'b': + simulatedopcount = getnum(optarg, &endp); + if (!quiet) + fprintf(stdout, "Will begin at operation %lu\n", + simulatedopcount); + if (simulatedopcount == 0) + usage(); + simulatedopcount -= 1; + break; + case 'c': + closeprob = getnum(optarg, &endp); + if (!quiet) + fprintf(stdout, + "Chance of close/open is 1 in %d\n", + closeprob); + if (closeprob <= 0) + usage(); + break; + case 'd': + debug = 1; + break; + case 'f': + flush_enabled = 1; + break; + case 'g': + deep_copy = 1; + break; + case 'h': + holebdy = getnum(optarg, &endp); + if (holebdy <= 0) + usage(); + break; + case 'j': + journal_replay = true; + break; + case 'k': + keep_on_success = 1; + break; + case 'l': + { + int _num = getnum(optarg, &endp); + if (_num <= 0) + usage(); + maxfilelen = _num; + } + break; + case 'm': + monitorstart = getnum(optarg, &endp); + if (monitorstart < 0) + usage(); + if (!endp || *endp++ != ':') + usage(); + monitorend = getnum(endp, &endp); + if (monitorend < 0) + usage(); + if (monitorend == 0) + monitorend = -1; /* aka infinity */ + debug = 1; + break; + case 'n': + sizechecks = 0; + break; + case 'o': + maxoplen = getnum(optarg, &endp); + if (maxoplen <= 0) + usage(); + break; + case 'p': + progressinterval = getnum(optarg, &endp); + if (progressinterval == 0) + usage(); + break; + case 'q': + quiet = 1; + break; + case 'r': + readbdy = getnum(optarg, &endp); + if (readbdy <= 0) + usage(); + break; + case 's': + style = getnum(optarg, &endp); + if (style < 0 || style > 1) + usage(); + break; + case 't': + truncbdy = getnum(optarg, &endp); + if (truncbdy <= 0) + usage(); + break; + case 'w': + writebdy = getnum(optarg, &endp); + if (writebdy <= 0) + usage(); + break; + case 'x': + prealloc = 1; + break; + case 'y': + do_fsync = 1; + break; + case 'C': + clone_calls = 0; + break; + case 'D': + debugstart = getnum(optarg, &endp); + if (debugstart < 1) + usage(); + break; + case 'F': + fallocate_calls = 0; + break; +#if defined(__FreeBSD__) + case 'G': + prt("rbd-ggate mode enabled\n"); + ops = &ggate_operations; + break; +#endif + case 'H': + punch_hole_calls = 0; + break; +#if defined(WITH_KRBD) + case 'K': + prt("krbd mode enabled\n"); + ops = &krbd_operations; + break; +#endif +#if defined(__linux__) + case 'M': + prt("rbd-nbd mode enabled\n"); + ops = &nbd_operations; + break; +#endif + case 'L': + lite = 1; + break; + case 'N': + numops = getnum(optarg, &endp); + if (numops < 0) + usage(); + break; + case 'O': + randomoplen = 0; + break; + case 'P': + strncpy(dirpath, optarg, sizeof(dirpath)-1); + dirpath[sizeof(dirpath)-1] = '\0'; + strncpy(goodfile, dirpath, sizeof(goodfile)-1); + goodfile[sizeof(goodfile)-1] = '\0'; + if (strlen(goodfile) < sizeof(goodfile)-2) { + strcat(goodfile, "/"); + } else { + prt("file name to long\n"); + exit(1); + } + strncpy(logfile, dirpath, sizeof(logfile)-1); + logfile[sizeof(logfile)-1] = '\0'; + if (strlen(logfile) < sizeof(logfile)-2) { + strcat(logfile, "/"); + } else { + prt("file path to long\n"); + exit(1); + } + break; + case 'R': + mapped_reads = 0; + if (!quiet) + fprintf(stdout, "mapped reads DISABLED\n"); + break; + case 'S': + seed = getnum(optarg, &endp); + if (seed == 0) + seed = std::random_device()() % 10000; + if (!quiet) + fprintf(stdout, "Seed set to %d\n", seed); + if (seed < 0) + usage(); + break; + case 'U': + randomize_striping = 0; + break; + case 'W': + mapped_writes = 0; + if (!quiet) + fprintf(stdout, "mapped writes DISABLED\n"); + break; + case 'Z': + o_direct = O_DIRECT; + break; + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + if (argc != 2) + usage(); + pool = argv[0]; + iname = argv[1]; + + signal(SIGHUP, cleanup); + signal(SIGINT, cleanup); + signal(SIGPIPE, cleanup); + signal(SIGALRM, cleanup); + signal(SIGTERM, cleanup); + signal(SIGXCPU, cleanup); + signal(SIGXFSZ, cleanup); + signal(SIGVTALRM, cleanup); + signal(SIGUSR1, cleanup); + signal(SIGUSR2, cleanup); + + random_generator.seed(seed); + + if (lite) { + file_size = maxfilelen; + } + + ret = create_image(); + if (ret < 0) { + prterrcode(iname, ret); + exit(90); + } + ret = ops->open(iname, &ctx); + if (ret < 0) { + simple_err("Error opening image", ret); + exit(91); + } + if (!dirpath[0]) + strcat(dirpath, "."); + strncat(goodfile, iname, 256); + strcat (goodfile, ".fsxgood"); + fsxgoodfd = open(goodfile, O_RDWR|O_CREAT|O_TRUNC, 0666); + if (fsxgoodfd < 0) { + prterr(goodfile); + exit(92); + } + strncat(logfile, iname, 256); + strcat (logfile, ".fsxlog"); + fsxlogf = fopen(logfile, "w"); + if (fsxlogf == NULL) { + prterr(logfile); + exit(93); + } + + original_buf = (char *) malloc(maxfilelen); + for (i = 0; i < (int)maxfilelen; i++) + original_buf[i] = get_random() % 256; + + ret = posix_memalign((void **)&good_buf, + std::max(writebdy, (int)sizeof(void *)), maxfilelen); + if (ret > 0) { + if (ret == EINVAL) + prt("writebdy is not a suitable power of two\n"); + else + prterrcode("main: posix_memalign(good_buf)", -ret); + exit(94); + } + memset(good_buf, '\0', maxfilelen); + + ret = posix_memalign((void **)&temp_buf, + std::max(readbdy, (int)sizeof(void *)), maxfilelen); + if (ret > 0) { + if (ret == EINVAL) + prt("readbdy is not a suitable power of two\n"); + else + prterrcode("main: posix_memalign(temp_buf)", -ret); + exit(95); + } + memset(temp_buf, '\0', maxfilelen); + + if (lite) { /* zero entire existing file */ + ssize_t written; + + written = ops->write(&ctx, 0, (size_t)maxfilelen, good_buf); + if (written != (ssize_t)maxfilelen) { + if (written < 0) { + prterrcode(iname, written); + warn("main: error on write"); + } else + warn("main: short write, 0x%x bytes instead " + "of 0x%lx\n", + (unsigned)written, + maxfilelen); + exit(98); + } + } else + check_trunc_hack(); + + //test_fallocate(); + + while (numops == -1 || numops--) + test(); + + ret = ops->close(&ctx); + if (ret < 0) { + prterrcode("ops->close", ret); + report_failure(99); + } + + if (journal_replay) { + char imagename[1024]; + clone_imagename(imagename, sizeof(imagename), num_clones); + ret = finalize_journal(ioctx, imagename, num_clones, 0, 0, 0); + if (ret < 0) { + report_failure(100); + } + } + + if (num_clones > 0) { + if (journal_replay) { + check_clone(num_clones - 1, true); + } + check_clone(num_clones - 1, false); + } + + if (!keep_on_success) { + while (num_clones >= 0) { + static bool remove_snap = false; + + if (journal_replay) { + char replayimagename[1024]; + replay_imagename(replayimagename, + sizeof(replayimagename), + num_clones); + remove_image(ioctx, replayimagename, + remove_snap, + false); + } + + char clonename[128]; + clone_imagename(clonename, 128, num_clones); + remove_image(ioctx, clonename, remove_snap, + journal_replay); + + remove_snap = true; + num_clones--; + } + } + + prt("All operations completed A-OK!\n"); + fclose(fsxlogf); + + rados_ioctx_destroy(ioctx); +#if defined(WITH_KRBD) + krbd_destroy(krbd); +#endif + rados_shutdown(cluster); + + free(original_buf); + free(good_buf); + free(temp_buf); + + exit(0); + return 0; +} 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 00000000..14f9a734 --- /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<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 diff --git a/src/test/librbd/image/test_mock_AttachParentRequest.cc b/src/test/librbd/image/test_mock_AttachParentRequest.cc new file mode 100644 index 00000000..c8c6ca71 --- /dev/null +++ b/src/test/librbd/image/test_mock_AttachParentRequest.cc @@ -0,0 +1,155 @@ +// -*- 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/image/AttachParentRequest.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 librbd + +// template definitions +#include "librbd/image/AttachParentRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockImageAttachParentRequest : public TestMockFixture { +public: + typedef AttachParentRequest<MockTestImageCtx> MockAttachParentRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); + } + + void expect_parent_attach(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("parent_attach"), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_set_parent(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("set_parent"), _, _, _)) + .WillOnce(Return(r)); + } + + librbd::ImageCtx *image_ctx; +}; + +TEST_F(TestMockImageAttachParentRequest, ParentAttachSuccess) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_attach(mock_image_ctx, 0); + + cls::rbd::ParentImageSpec parent_image_spec{ + 1, "ns", "image id", 123}; + + C_SaferCond ctx; + auto req = MockAttachParentRequest::create(mock_image_ctx, parent_image_spec, + 234, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageAttachParentRequest, SetParentSuccess) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_attach(mock_image_ctx, -EOPNOTSUPP); + expect_set_parent(mock_image_ctx, 0); + + cls::rbd::ParentImageSpec parent_image_spec{ + 1, "", "image id", 123}; + + C_SaferCond ctx; + auto req = MockAttachParentRequest::create(mock_image_ctx, parent_image_spec, + 234, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageAttachParentRequest, ParentAttachError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_attach(mock_image_ctx, -EPERM); + + cls::rbd::ParentImageSpec parent_image_spec{ + 1, "", "image id", 123}; + + C_SaferCond ctx; + auto req = MockAttachParentRequest::create(mock_image_ctx, parent_image_spec, + 234, false, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageAttachParentRequest, SetParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_attach(mock_image_ctx, -EOPNOTSUPP); + expect_set_parent(mock_image_ctx, -EINVAL); + + cls::rbd::ParentImageSpec parent_image_spec{ + 1, "", "image id", 123}; + + C_SaferCond ctx; + auto req = MockAttachParentRequest::create(mock_image_ctx, parent_image_spec, + 234, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageAttachParentRequest, NamespaceUnsupported) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_attach(mock_image_ctx, -EOPNOTSUPP); + + cls::rbd::ParentImageSpec parent_image_spec{ + 1, "ns", "image id", 123}; + + C_SaferCond ctx; + auto req = MockAttachParentRequest::create(mock_image_ctx, parent_image_spec, + 234, false, &ctx); + req->send(); + ASSERT_EQ(-EXDEV, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_CloneRequest.cc b/src/test/librbd/image/test_mock_CloneRequest.cc new file mode 100644 index 00000000..fd63ded4 --- /dev/null +++ b/src/test/librbd/image/test_mock_CloneRequest.cc @@ -0,0 +1,934 @@ +// -*- 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 "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/image/TypeTraits.h" +#include "librbd/image/AttachChildRequest.h" +#include "librbd/image/AttachParentRequest.h" +#include "librbd/image/CreateRequest.h" +#include "librbd/image/RemoveRequest.h" +#include "librbd/mirror/EnableRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + static MockTestImageCtx* s_instance; + static MockTestImageCtx* create(const std::string &image_name, + const std::string &image_id, + const char *snap, librados::IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + static MockTestImageCtx* create(const std::string &image_name, + const std::string &image_id, + librados::snap_t snap_id, IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + s_instance = this; + } +}; + +MockTestImageCtx* MockTestImageCtx::s_instance = nullptr; + +} // anonymous namespace + +namespace image { + +template <> +struct AttachChildRequest<MockTestImageCtx> { + uint32_t clone_format; + Context* on_finish = nullptr; + static AttachChildRequest* s_instance; + static AttachChildRequest* create(MockTestImageCtx *, + MockTestImageCtx *, + const librados::snap_t &, + MockTestImageCtx *, + const librados::snap_t &, + uint32_t clone_format, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->clone_format = clone_format; + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + AttachChildRequest() { + s_instance = this; + } +}; + +AttachChildRequest<MockTestImageCtx>* AttachChildRequest<MockTestImageCtx>::s_instance = nullptr; + +template <> +struct AttachParentRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static AttachParentRequest* s_instance; + static AttachParentRequest* create(MockTestImageCtx&, + const cls::rbd::ParentImageSpec& pspec, + uint64_t parent_overlap, bool reattach, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + AttachParentRequest() { + s_instance = this; + } +}; + +AttachParentRequest<MockTestImageCtx>* AttachParentRequest<MockTestImageCtx>::s_instance = nullptr; + +template <> +struct CreateRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static CreateRequest* s_instance; + static CreateRequest* create(const ConfigProxy& config, IoCtx &ioctx, + const std::string &image_name, + const std::string &image_id, uint64_t size, + const ImageOptions &image_options, + const std::string &non_primary_global_image_id, + const std::string &primary_mirror_uuid, + bool skip_mirror_enable, + ContextWQ *op_work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + CreateRequest() { + s_instance = this; + } +}; + +CreateRequest<MockTestImageCtx>* CreateRequest<MockTestImageCtx>::s_instance = nullptr; + +template <> +struct RemoveRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static RemoveRequest* s_instance; + static RemoveRequest* create(librados::IoCtx &ioctx, + const std::string &image_name, + const std::string &image_id, + bool force, bool from_trash_remove, + ProgressContext &prog_ctx, + ContextWQ *op_work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + RemoveRequest() { + s_instance = this; + } +}; + +RemoveRequest<MockTestImageCtx>* RemoveRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace image + +namespace mirror { + +template <> +struct EnableRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static EnableRequest* s_instance; + static EnableRequest* create(librados::IoCtx &io_ctx, + const std::string &image_id, + const std::string &non_primary_global_image_id, + MockContextWQ *op_work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + EnableRequest() { + s_instance = this; + } +}; + +EnableRequest<MockTestImageCtx>* EnableRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace mirror +} // namespace librbd + +// template definitions +#include "librbd/image/CloneRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockImageCloneRequest : public TestMockFixture { +public: + typedef CloneRequest<MockTestImageCtx> MockCloneRequest; + typedef AttachChildRequest<MockTestImageCtx> MockAttachChildRequest; + typedef AttachParentRequest<MockTestImageCtx> MockAttachParentRequest; + typedef CreateRequest<MockTestImageCtx> MockCreateRequest; + typedef RemoveRequest<MockTestImageCtx> MockRemoveRequest; + typedef mirror::EnableRequest<MockTestImageCtx> MockMirrorEnableRequest; + + 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")); + 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_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)) + .WillOnce(Return(size)); + } + + 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_create(MockCreateRequest& mock_create_request, int r) { + EXPECT_CALL(mock_create_request, send()) + .WillOnce(Invoke([this, &mock_create_request, r]() { + image_ctx->op_work_queue->queue(mock_create_request.on_finish, r); + })); + } + + void expect_open(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, open(true, _)) + .WillOnce(WithArg<1>(Invoke([this, r](Context* ctx) { + image_ctx->op_work_queue->queue(ctx, r); + }))); + if (r < 0) { + EXPECT_CALL(mock_image_ctx, destroy()); + } + } + + void expect_attach_parent(MockAttachParentRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(Invoke([this, &mock_request, r]() { + image_ctx->op_work_queue->queue(mock_request.on_finish, r); + })); + } + + void expect_attach_child(MockAttachChildRequest& mock_request, + uint32_t clone_format, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(Invoke([this, &mock_request, clone_format, r]() { + EXPECT_EQ(mock_request.clone_format, clone_format); + image_ctx->op_work_queue->queue(mock_request.on_finish, r); + })); + } + + void expect_metadata_list(MockTestImageCtx &mock_image_ctx, + const std::map<std::string, bufferlist>& metadata, + int r) { + bufferlist out_bl; + encode(metadata, out_bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("metadata_list"), _, _, _)) + .WillOnce(WithArg<5>(Invoke([out_bl, r](bufferlist *out) { + *out = out_bl; + return r; + }))); + } + + void expect_metadata_set(librados::IoCtx& io_ctx, + MockTestImageCtx& mock_image_ctx, + const std::map<std::string, bufferlist>& metadata, + int r) { + bufferlist in_bl; + encode(metadata, in_bl); + + EXPECT_CALL(get_mock_io_ctx(io_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("metadata_set"), + ContentsEqual(in_bl), _, _)) + .WillOnce(Return(r)); + } + + void expect_test_features(MockTestImageCtx &mock_image_ctx, + uint64_t features, bool enabled) { + EXPECT_CALL(mock_image_ctx, test_features(features)) + .WillOnce(Return(enabled)); + } + + void expect_mirror_mode_get(MockTestImageCtx &mock_image_ctx, + cls::rbd::MirrorMode mirror_mode, int r) { + bufferlist out_bl; + encode(static_cast<uint32_t>(mirror_mode), out_bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_mode_get"), + _, _, _)) + .WillOnce(WithArg<5>(Invoke([out_bl, r](bufferlist* out) { + *out = out_bl; + return r; + }))); + } + + void expect_mirror_enable(MockMirrorEnableRequest& mock_mirror_enable_request, + int r) { + EXPECT_CALL(mock_mirror_enable_request, send()) + .WillOnce(Invoke([this, &mock_mirror_enable_request, r]() { + image_ctx->op_work_queue->queue(mock_mirror_enable_request.on_finish, r); + })); + } + + void expect_close(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, close(_)) + .WillOnce(Invoke([this, r](Context* ctx) { + image_ctx->op_work_queue->queue(ctx, r); + })); + EXPECT_CALL(mock_image_ctx, destroy()); + } + + void expect_remove(MockRemoveRequest& mock_remove_request, int r) { + EXPECT_CALL(mock_remove_request, send()) + .WillOnce(Invoke([this, &mock_remove_request, r]() { + image_ctx->op_work_queue->queue(mock_remove_request.on_finish, r); + })); + } + + librbd::ImageCtx *image_ctx; +}; + +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_open(mock_image_ctx, 0); + + 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); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 1, 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); + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + 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); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "2")); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 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); + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + 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_open(mock_image_ctx, 0); + + 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); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 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); + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, OpenParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, CreateError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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, -EINVAL); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, OpenError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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, -EINVAL); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, AttachParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, -EINVAL); + + expect_close(mock_image_ctx, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, AttachChildError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, -EINVAL); + + expect_close(mock_image_ctx, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, MetadataListError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + expect_metadata_list(mock_image_ctx, {{"key", {}}}, -EINVAL); + + expect_close(mock_image_ctx, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, MetadataSetError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + expect_metadata_list(mock_image_ctx, {{"key", {}}}, 0); + expect_metadata_set(m_ioctx, mock_image_ctx, {{"key", {}}}, -EINVAL); + + expect_close(mock_image_ctx, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, GetMirrorModeError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_JOURNALING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + expect_metadata_list(mock_image_ctx, {}, 0); + + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, true); + expect_mirror_mode_get(mock_image_ctx, cls::rbd::MIRROR_MODE_POOL, -EINVAL); + + expect_close(mock_image_ctx, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, MirrorEnableError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_JOURNALING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + expect_metadata_list(mock_image_ctx, {}, 0); + + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, true); + expect_mirror_mode_get(mock_image_ctx, cls::rbd::MIRROR_MODE_POOL, 0); + + MockMirrorEnableRequest mock_mirror_enable_request; + expect_mirror_enable(mock_mirror_enable_request, -EINVAL); + + expect_close(mock_image_ctx, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, CloseError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + expect_metadata_list(mock_image_ctx, {}, 0); + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, false); + + expect_close(mock_image_ctx, -EINVAL); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, RemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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, -EINVAL); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, -EPERM); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, CloseParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + 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, -EINVAL); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, -EPERM); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", 123, + m_ioctx, "clone name", "clone id", clone_opts, + "", "", image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_DetachChildRequest.cc b/src/test/librbd/image/test_mock_DetachChildRequest.cc new file mode 100644 index 00000000..d5407daf --- /dev/null +++ b/src/test/librbd/image/test_mock_DetachChildRequest.cc @@ -0,0 +1,356 @@ +// -*- 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 "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/image/DetachChildRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + static MockTestImageCtx* s_instance; + static MockTestImageCtx* create(const std::string &image_name, + const std::string &image_id, + const char *snap, librados::IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + s_instance = this; + } +}; + +MockTestImageCtx* MockTestImageCtx::s_instance = nullptr; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/image/DetachChildRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockImageDetachChildRequest : public TestMockFixture { +public: + typedef DetachChildRequest<MockTestImageCtx> MockDetachChildRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); + } + + void expect_test_op_features(MockTestImageCtx& mock_image_ctx, bool enabled) { + EXPECT_CALL(mock_image_ctx, + test_op_features(RBD_OPERATION_FEATURE_CLONE_CHILD)) + .WillOnce(Return(enabled)); + } + + void expect_create_ioctx(MockImageCtx &mock_image_ctx, + librados::MockTestMemIoCtxImpl **io_ctx_impl) { + *io_ctx_impl = &get_mock_io_ctx(mock_image_ctx.md_ctx); + auto rados_client = (*io_ctx_impl)->get_mock_rados_client(); + + EXPECT_CALL(*rados_client, create_ioctx(_, _)) + .WillOnce(DoAll(GetReference(*io_ctx_impl), Return(*io_ctx_impl))); + } + + void expect_child_detach(MockImageCtx &mock_image_ctx, + librados::MockTestMemIoCtxImpl &mock_io_ctx_impl, + int r) { + auto& parent_spec = mock_image_ctx.parent_md.spec; + + bufferlist bl; + encode(parent_spec.snap_id, bl); + encode(cls::rbd::ChildImageSpec{mock_image_ctx.md_ctx.get_id(), "", + mock_image_ctx.id}, bl); + + EXPECT_CALL(mock_io_ctx_impl, + exec(util::header_name(parent_spec.image_id), + _, StrEq("rbd"), StrEq("child_detach"), ContentsEqual(bl), + _, _)) + .WillOnce(Return(r)); + } + + void expect_remove_child(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_CHILDREN, _, StrEq("rbd"), StrEq("remove_child"), _, + _, _)) + .WillOnce(Return(r)); + } + + void expect_snapshot_get(MockImageCtx &mock_image_ctx, + librados::MockTestMemIoCtxImpl &mock_io_ctx_impl, + const std::string& parent_header_name, + const cls::rbd::SnapshotInfo& snap_info, int r) { + + using ceph::encode; + EXPECT_CALL(mock_io_ctx_impl, + exec(parent_header_name, _, StrEq("rbd"), + StrEq("snapshot_get"), _, _, _)) + .WillOnce(WithArg<5>(Invoke([snap_info, r](bufferlist* bl) { + encode(snap_info, *bl); + return r; + }))); + } + + void expect_open(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, open(true, _)) + .WillOnce(WithArg<1>(Invoke([this, r](Context* ctx) { + image_ctx->op_work_queue->queue(ctx, r); + }))); + if (r == 0) { + EXPECT_CALL(mock_image_ctx, test_features(_)) + .WillOnce(Return(false)); + } + } + + void expect_close(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, close(_)) + .WillOnce(Invoke([this, r](Context* ctx) { + image_ctx->op_work_queue->queue(ctx, r); + })); + EXPECT_CALL(mock_image_ctx, destroy()); + } + + void expect_snap_remove(MockImageCtx &mock_image_ctx, + const std::string &snap_name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, + snap_remove({cls::rbd::TrashSnapshotNamespace{}}, + StrEq(snap_name), _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context *ctx) { + image_ctx->op_work_queue->queue(ctx, r); + }))); + } + + librbd::ImageCtx *image_ctx; +}; + +TEST_F(TestMockImageDetachChildRequest, SuccessV1) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, false); + expect_remove_child(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, SuccessV2) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotSuccess) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + expect_open(mock_image_ctx, 0); + expect_snap_remove(mock_image_ctx, "snap1", 0); + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotInUse) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 1}, 0); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotSnapshotGetError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 0}, -EINVAL); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotOpenParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + expect_open(mock_image_ctx, -EPERM); + EXPECT_CALL(mock_image_ctx, destroy()); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotRemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + expect_open(mock_image_ctx, 0); + expect_snap_remove(mock_image_ctx, "snap1", -EPERM); + expect_close(mock_image_ctx, -EPERM); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, ParentDNE) { + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, ChildDetachError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, -EPERM); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, RemoveChildError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, false); + expect_remove_child(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_DetachParentRequest.cc b/src/test/librbd/image/test_mock_DetachParentRequest.cc new file mode 100644 index 00000000..3d7a0e32 --- /dev/null +++ b/src/test/librbd/image/test_mock_DetachParentRequest.cc @@ -0,0 +1,135 @@ +// -*- 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/image/DetachParentRequest.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 librbd + +// template definitions +#include "librbd/image/DetachParentRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockImageDetachParentRequest : public TestMockFixture { +public: + typedef DetachParentRequest<MockTestImageCtx> MockDetachParentRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); + } + + void expect_parent_detach(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("parent_detach"), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_remove_parent(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("remove_parent"), _, _, _)) + .WillOnce(Return(r)); + } + + librbd::ImageCtx *image_ctx; +}; + +TEST_F(TestMockImageDetachParentRequest, ParentDetachSuccess) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_detach(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockDetachParentRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachParentRequest, RemoveParentSuccess) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_detach(mock_image_ctx, -EOPNOTSUPP); + expect_remove_parent(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockDetachParentRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachParentRequest, ParentDNE) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_detach(mock_image_ctx, -ENOENT); + + C_SaferCond ctx; + auto req = MockDetachParentRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachParentRequest, ParentDetachError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_detach(mock_image_ctx, -EPERM); + + C_SaferCond ctx; + auto req = MockDetachParentRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageDetachParentRequest, RemoveParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_detach(mock_image_ctx, -EOPNOTSUPP); + expect_remove_parent(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockDetachParentRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_ListWatchersRequest.cc b/src/test/librbd/image/test_mock_ListWatchersRequest.cc new file mode 100644 index 00000000..d90fc4ab --- /dev/null +++ b/src/test/librbd/image/test_mock_ListWatchersRequest.cc @@ -0,0 +1,212 @@ +// -*- 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 "librbd/image/ListWatchersRequest.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "test/librbd/mock/MockContextWQ.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/test_support.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/image/ListWatchersRequest.cc" +template class librbd::image::ListWatchersRequest<librbd::MockImageCtx>; + +namespace librbd { + +namespace image { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; + +class TestMockListWatchersRequest : public TestMockFixture { +public: + typedef ListWatchersRequest<MockImageCtx> MockListWatchersRequest; + + obj_watch_t watcher(const std::string &address, uint64_t watch_handle) { + obj_watch_t w; + strcpy(w.addr, address.c_str()); + w.watcher_id = 0; + w.cookie = watch_handle; + w.timeout_seconds = 0; + + return w; + } + + void expect_list_watchers(MockTestImageCtx &mock_image_ctx, + const std::string oid, + const std::list<obj_watch_t> &watchers, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + list_watchers(oid, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoAll(SetArgPointee<1>(watchers), Return(0))); + } + } + + void expect_list_image_watchers(MockTestImageCtx &mock_image_ctx, + const std::list<obj_watch_t> &watchers, + int r) { + expect_list_watchers(mock_image_ctx, mock_image_ctx.header_oid, + watchers, r); + } + + void expect_list_mirror_watchers(MockTestImageCtx &mock_image_ctx, + const std::list<obj_watch_t> &watchers, + int r) { + expect_list_watchers(mock_image_ctx, RBD_MIRRORING, watchers, r); + } + + void expect_get_watch_handle(MockImageWatcher &mock_watcher, + uint64_t watch_handle) { + EXPECT_CALL(mock_watcher, get_watch_handle()) + .WillOnce(Return(watch_handle)); + } +}; + +TEST_F(TestMockListWatchersRequest, NoImageWatchers) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageWatcher mock_watcher; + + InSequence seq; + expect_list_image_watchers(mock_image_ctx, {}, 0); + + std::list<obj_watch_t> watchers; + C_SaferCond ctx; + auto req = MockListWatchersRequest::create(mock_image_ctx, 0, &watchers, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE(watchers.empty()); +} + +TEST_F(TestMockListWatchersRequest, Error) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageWatcher mock_watcher; + + InSequence seq; + expect_list_image_watchers(mock_image_ctx, {}, -EINVAL); + + std::list<obj_watch_t> watchers; + C_SaferCond ctx; + auto req = MockListWatchersRequest::create(mock_image_ctx, 0, &watchers, + &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockListWatchersRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageWatcher mock_watcher; + + InSequence seq; + expect_list_image_watchers(mock_image_ctx, + {watcher("a", 123), watcher("b", 456)}, 0); + expect_get_watch_handle(*mock_image_ctx.image_watcher, 123); + + std::list<obj_watch_t> watchers; + C_SaferCond ctx; + auto req = MockListWatchersRequest::create(mock_image_ctx, 0, &watchers, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(2U, watchers.size()); + + auto w = watchers.begin(); + ASSERT_STREQ("a", w->addr); + ASSERT_EQ(123U, w->cookie); + + w++; + ASSERT_STREQ("b", w->addr); + ASSERT_EQ(456U, w->cookie); +} + +TEST_F(TestMockListWatchersRequest, FilterOutMyInstance) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageWatcher mock_watcher; + + InSequence seq; + expect_list_image_watchers(mock_image_ctx, + {watcher("a", 123), watcher("b", 456)}, 0); + expect_get_watch_handle(*mock_image_ctx.image_watcher, 123); + + std::list<obj_watch_t> watchers; + C_SaferCond ctx; + auto req = MockListWatchersRequest::create( + mock_image_ctx, LIST_WATCHERS_FILTER_OUT_MY_INSTANCE, &watchers, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(1U, watchers.size()); + + ASSERT_STREQ("b", watchers.begin()->addr); + ASSERT_EQ(456U, watchers.begin()->cookie); +} + +TEST_F(TestMockListWatchersRequest, FilterOutMirrorInstance) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageWatcher mock_watcher; + + InSequence seq; + expect_list_image_watchers(mock_image_ctx, + {watcher("a", 123), watcher("b", 456)}, 0); + expect_list_mirror_watchers(mock_image_ctx, {watcher("b", 789)}, 0); + expect_get_watch_handle(*mock_image_ctx.image_watcher, 123); + + std::list<obj_watch_t> watchers; + C_SaferCond ctx; + auto req = MockListWatchersRequest::create( + mock_image_ctx, LIST_WATCHERS_FILTER_OUT_MIRROR_INSTANCES, &watchers, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(1U, watchers.size()); + + ASSERT_STREQ("a", watchers.begin()->addr); + ASSERT_EQ(123U, watchers.begin()->cookie); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_PreRemoveRequest.cc b/src/test/librbd/image/test_mock_PreRemoveRequest.cc new file mode 100644 index 00000000..65a20db9 --- /dev/null +++ b/src/test/librbd/image/test_mock_PreRemoveRequest.cc @@ -0,0 +1,446 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/image/ListWatchersRequest.h" +#include "librbd/image/PreRemoveRequest.h" +#include "librbd/image/RefreshParentRequest.h" +#include "librbd/operation/SnapshotRemoveRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <list> +#include <boost/scope_exit.hpp> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace operation { + +template <> +class SnapshotRemoveRequest<MockTestImageCtx> { +public: + static SnapshotRemoveRequest *s_instance; + static SnapshotRemoveRequest *create(MockTestImageCtx &image_ctx, + cls::rbd::SnapshotNamespace sn, + std::string name, + uint64_t id, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + SnapshotRemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +SnapshotRemoveRequest<MockTestImageCtx> *SnapshotRemoveRequest<MockTestImageCtx>::s_instance; + +} // namespace operation + +namespace image { + +template<> +class ListWatchersRequest<MockTestImageCtx> { +public: + static ListWatchersRequest *s_instance; + Context *on_finish = nullptr; + + static ListWatchersRequest *create(MockTestImageCtx &image_ctx, int flags, + std::list<obj_watch_t> *watchers, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + ListWatchersRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +ListWatchersRequest<MockTestImageCtx> *ListWatchersRequest<MockTestImageCtx>::s_instance; + +} // namespace image +} // namespace librbd + +// template definitions +#include "librbd/image/PreRemoveRequest.cc" + +ACTION_P(TestFeatures, image_ctx) { + return ((image_ctx->features & arg0) != 0); +} + +ACTION_P(ShutDownExclusiveLock, image_ctx) { + // shutting down exclusive lock will close object map and journal + image_ctx->exclusive_lock = nullptr; + image_ctx->object_map = nullptr; + image_ctx->journal = nullptr; +} + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockImagePreRemoveRequest : public TestMockFixture { +public: + typedef PreRemoveRequest<MockTestImageCtx> MockPreRemoveRequest; + typedef ListWatchersRequest<MockTestImageCtx> MockListWatchersRequest; + typedef librbd::operation::SnapshotRemoveRequest<MockTestImageCtx> MockSnapshotRemoveRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_test_imctx)); + m_mock_imctx = new MockTestImageCtx(*m_test_imctx); + } + + void TearDown() override { + delete m_mock_imctx; + TestMockFixture::TearDown(); + } + + void expect_test_features(MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_)) + .WillRepeatedly(TestFeatures(&mock_image_ctx)); + } + + void expect_set_journal_policy(MockTestImageCtx &mock_image_ctx) { + if (m_test_imctx->test_features(RBD_FEATURE_JOURNALING)) { + EXPECT_CALL(mock_image_ctx, set_journal_policy(_)) + .WillOnce(Invoke([](journal::Policy* policy) { + ASSERT_TRUE(policy->journal_disabled()); + delete policy; + })); + } + } + + void expect_try_acquire_exclusive_lock(MockTestImageCtx &mock_image_ctx, + MockExclusiveLock &mock_exclusive_lock, + int r) { + if (m_mock_imctx->exclusive_lock != nullptr) { + EXPECT_CALL(mock_exclusive_lock, try_acquire_lock(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + } + + void expect_shut_down_exclusive_lock(MockTestImageCtx &mock_image_ctx, + MockExclusiveLock &mock_exclusive_lock, + int r) { + if (m_mock_imctx->exclusive_lock != nullptr) { + EXPECT_CALL(mock_exclusive_lock, shut_down(_)) + .WillOnce(DoAll(ShutDownExclusiveLock(&mock_image_ctx), + CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_is_exclusive_lock_owner(MockTestImageCtx &mock_image_ctx, + MockExclusiveLock &mock_exclusive_lock, + bool is_owner) { + if (m_mock_imctx->exclusive_lock != nullptr) { + EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillOnce(Return(is_owner)); + } + } + + void expect_list_image_watchers( + MockTestImageCtx &mock_image_ctx, + MockListWatchersRequest &mock_list_watchers_request, int r) { + EXPECT_CALL(mock_list_watchers_request, send()) + .WillOnce(FinishRequest(&mock_list_watchers_request, r, &mock_image_ctx)); + } + + void expect_get_group(MockTestImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.old_format) { + return; + } + + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("image_group_get"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_remove_snap(MockTestImageCtx &mock_image_ctx, + MockSnapshotRemoveRequest& mock_snap_remove_request, + int r) { + EXPECT_CALL(mock_snap_remove_request, send()) + .WillOnce(FinishRequest(&mock_snap_remove_request, r, &mock_image_ctx)); + } + + librbd::ImageCtx *m_test_imctx = nullptr; + MockTestImageCtx *m_mock_imctx = nullptr; +}; + +TEST_F(TestMockImagePreRemoveRequest, Success) { + MockExclusiveLock mock_exclusive_lock; + if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_journal_policy(*m_mock_imctx); + expect_try_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0); + expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, true); + + MockListWatchersRequest mock_list_watchers_request; + expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0); + + expect_get_group(*m_mock_imctx, 0); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, OperationsDisabled) { + REQUIRE_FORMAT_V2(); + + m_mock_imctx->operations_disabled = true; + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EROFS, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, ExclusiveLockTryAcquireFailed) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + MockExclusiveLock mock_exclusive_lock; + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_journal_policy(*m_mock_imctx); + expect_try_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, + -EINVAL); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, ExclusiveLockTryAcquireNotLockOwner) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + MockExclusiveLock mock_exclusive_lock; + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_journal_policy(*m_mock_imctx); + expect_try_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0); + expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, false); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, Force) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + MockExclusiveLock *mock_exclusive_lock = new MockExclusiveLock(); + m_mock_imctx->exclusive_lock = mock_exclusive_lock; + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_journal_policy(*m_mock_imctx); + expect_try_acquire_exclusive_lock(*m_mock_imctx, *mock_exclusive_lock, + -EINVAL); + expect_shut_down_exclusive_lock(*m_mock_imctx, *mock_exclusive_lock, 0); + + MockListWatchersRequest mock_list_watchers_request; + expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0); + + expect_get_group(*m_mock_imctx, 0); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, true, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, ExclusiveLockShutDownFailed) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + MockExclusiveLock *mock_exclusive_lock = new MockExclusiveLock(); + m_mock_imctx->exclusive_lock = mock_exclusive_lock; + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_journal_policy(*m_mock_imctx); + expect_try_acquire_exclusive_lock(*m_mock_imctx, *mock_exclusive_lock, -EINVAL); + expect_shut_down_exclusive_lock(*m_mock_imctx, *mock_exclusive_lock, -EINVAL); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, true, &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, Migration) { + m_mock_imctx->features |= RBD_FEATURE_MIGRATING; + + expect_test_features(*m_mock_imctx); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, Snapshots) { + m_mock_imctx->snap_info = { + {123, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}}}; + + expect_test_features(*m_mock_imctx); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-ENOTEMPTY, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, Watchers) { + MockExclusiveLock mock_exclusive_lock; + if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_journal_policy(*m_mock_imctx); + expect_try_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0); + expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, true); + + MockListWatchersRequest mock_list_watchers_request; + expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, + -EINVAL); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, GroupError) { + REQUIRE_FORMAT_V2(); + + MockExclusiveLock mock_exclusive_lock; + if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_journal_policy(*m_mock_imctx); + expect_try_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0); + expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, true); + + MockListWatchersRequest mock_list_watchers_request; + expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0); + + expect_get_group(*m_mock_imctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, AutoDeleteSnapshots) { + REQUIRE_FORMAT_V2(); + + MockExclusiveLock mock_exclusive_lock; + if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + m_mock_imctx->snap_info = { + {123, {"snap1", {cls::rbd::TrashSnapshotNamespace{}}, {}, {}, {}, {}, {}}}}; + + InSequence seq; + expect_set_journal_policy(*m_mock_imctx); + expect_try_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0); + expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, true); + + MockListWatchersRequest mock_list_watchers_request; + expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0); + + expect_get_group(*m_mock_imctx, 0); + + MockSnapshotRemoveRequest mock_snap_remove_request; + expect_remove_snap(*m_mock_imctx, mock_snap_remove_request, 0); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_RefreshRequest.cc b/src/test/librbd/image/test_mock_RefreshRequest.cc new file mode 100644 index 00000000..a920948f --- /dev/null +++ b/src/test/librbd/image/test_mock_RefreshRequest.cc @@ -0,0 +1,1383 @@ +// -*- 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/MockImageWatcher.h" +#include "test/librbd/mock/MockJournal.h" +#include "test/librbd/mock/MockJournalPolicy.h" +#include "test/librbd/mock/MockObjectMap.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/Operations.h" +#include "librbd/api/Image.h" +#include "librbd/image/RefreshRequest.h" +#include "librbd/image/RefreshParentRequest.h" +#include "librbd/io/ImageDispatchSpec.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <list> +#include <boost/scope_exit.hpp> + +namespace librbd { + +namespace { + +struct MockRefreshImageCtx : public MockImageCtx { + MockRefreshImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace image { + +template <> +struct RefreshParentRequest<MockRefreshImageCtx> { + static RefreshParentRequest* s_instance; + static RefreshParentRequest* create(MockRefreshImageCtx &mock_image_ctx, + const ParentImageInfo &parent_md, + const MigrationInfo &migration_info, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + static bool is_refresh_required(MockRefreshImageCtx &mock_image_ctx, + const ParentImageInfo& parent_md, + const MigrationInfo &migration_info) { + ceph_assert(s_instance != nullptr); + return s_instance->is_refresh_required(); + } + + Context *on_finish = nullptr; + + RefreshParentRequest() { + s_instance = this; + } + + MOCK_CONST_METHOD0(is_refresh_required, bool()); + MOCK_METHOD0(send, void()); + MOCK_METHOD0(apply, void()); + MOCK_METHOD1(finalize, void(Context *)); +}; + +RefreshParentRequest<MockRefreshImageCtx>* RefreshParentRequest<MockRefreshImageCtx>::s_instance = nullptr; + +} // namespace image + +namespace io { + +template <> +struct ImageDispatchSpec<librbd::MockRefreshImageCtx> { + static ImageDispatchSpec* s_instance; + AioCompletion *aio_comp = nullptr; + + static ImageDispatchSpec* create_flush_request( + librbd::MockRefreshImageCtx &image_ctx, AioCompletion *aio_comp, + FlushSource flush_source, const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_comp = aio_comp; + return s_instance; + } + + MOCK_CONST_METHOD0(send, void()); + + ImageDispatchSpec() { + s_instance = this; + } +}; + +ImageDispatchSpec<librbd::MockRefreshImageCtx>* ImageDispatchSpec<librbd::MockRefreshImageCtx>::s_instance = nullptr; + +} // namespace io +namespace util { + +inline ImageCtx *get_image_ctx(librbd::MockRefreshImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util +} // namespace librbd + +// template definitions +#include "librbd/image/RefreshRequest.cc" + +ACTION_P(TestFeatures, image_ctx) { + return ((image_ctx->features & arg0) != 0); +} + +ACTION_P(ShutDownExclusiveLock, image_ctx) { + // shutting down exclusive lock will close object map and journal + image_ctx->exclusive_lock = nullptr; + image_ctx->object_map = nullptr; + image_ctx->journal = nullptr; +} + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::StrEq; + +class TestMockImageRefreshRequest : public TestMockFixture { +public: + typedef RefreshRequest<MockRefreshImageCtx> MockRefreshRequest; + typedef RefreshParentRequest<MockRefreshImageCtx> MockRefreshParentRequest; + typedef io::ImageDispatchSpec<librbd::MockRefreshImageCtx> MockIoImageDispatchSpec; + + void set_v1_migration_header(ImageCtx *ictx) { + bufferlist hdr; + ASSERT_EQ(0, read_header_bl(ictx->md_ctx, ictx->header_oid, hdr, nullptr)); + ASSERT_TRUE(hdr.length() >= sizeof(rbd_obj_header_ondisk)); + ASSERT_EQ(0, memcmp(RBD_HEADER_TEXT, hdr.c_str(), sizeof(RBD_HEADER_TEXT))); + + bufferlist::iterator it = hdr.begin(); + it.copy_in(sizeof(RBD_MIGRATE_HEADER_TEXT), RBD_MIGRATE_HEADER_TEXT); + ASSERT_EQ(0, ictx->md_ctx.write(ictx->header_oid, hdr, hdr.length(), 0)); + } + + void expect_set_require_lock(MockRefreshImageCtx &mock_image_ctx, + librbd::io::Direction direction, bool enabled) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, set_require_lock(direction, + enabled)); + } + + void expect_v1_read_header(MockRefreshImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + read(mock_image_ctx.header_oid, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_v1_get_snapshots(MockRefreshImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("snap_list"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_v1_get_locks(MockRefreshImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("lock"), StrEq("get_info"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_mutable_metadata(MockRefreshImageCtx &mock_image_ctx, + uint64_t features, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("get_size"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + uint64_t incompatible = ( + mock_image_ctx.read_only ? features & RBD_FEATURES_INCOMPATIBLE : + features & RBD_FEATURES_RW_INCOMPATIBLE); + + expect.WillOnce(DoDefault()); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("get_features"), _, _, _)) + .WillOnce(WithArg<5>(Invoke([features, incompatible](bufferlist* out_bl) { + encode(features, *out_bl); + encode(incompatible, *out_bl); + return 0; + }))); + expect_get_flags(mock_image_ctx, 0); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("get_snapcontext"), _, _, _)) + .WillOnce(DoDefault()); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("lock"), StrEq("get_info"), _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_parent_overlap_get(MockRefreshImageCtx &mock_image_ctx, int r) { + auto& expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("parent_overlap_get"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_parent(MockRefreshImageCtx &mock_image_ctx, int r) { + auto& expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("parent_get"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + expect_parent_overlap_get(mock_image_ctx, 0); + } + } + + void expect_get_parent_legacy(MockRefreshImageCtx &mock_image_ctx, int r) { + auto& expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("get_parent"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_migration_header(MockRefreshImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("migration_get"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_metadata(MockRefreshImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("metadata_list"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + EXPECT_CALL(*mock_image_ctx.image_watcher, is_unregistered()) + .WillOnce(Return(false)); + } + } + + void expect_get_flags(MockRefreshImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("get_flags"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_op_features(MockRefreshImageCtx &mock_image_ctx, + uint64_t op_features, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("op_features_get"), _, _, _)) + .WillOnce(WithArg<5>(Invoke([op_features, r](bufferlist* out_bl) { + encode(op_features, *out_bl); + return r; + }))); + } + + void expect_get_group(MockRefreshImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("image_group_get"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_snapshots(MockRefreshImageCtx &mock_image_ctx, + bool legacy_parent, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("snapshot_get"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + if (legacy_parent) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("get_parent"), _, _, _)) + .WillOnce(DoDefault()); + } else { + expect_parent_overlap_get(mock_image_ctx, 0); + } + expect_get_flags(mock_image_ctx, 0); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("get_protection_status"), _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_get_snapshots_legacy(MockRefreshImageCtx &mock_image_ctx, + bool include_timestamp, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("get_snapshot_name"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("get_size"), _, _, _)) + .WillOnce(DoDefault()); + if (include_timestamp) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("get_snapshot_timestamp"), _, _, _)) + .WillOnce(DoDefault()); + } + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("get_parent"), _, _, _)) + .WillOnce(DoDefault()); + expect_get_flags(mock_image_ctx, 0); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("get_protection_status"), _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_apply_metadata(MockRefreshImageCtx &mock_image_ctx, + int r) { + EXPECT_CALL(mock_image_ctx, apply_metadata(_, false)) + .WillOnce(Return(r)); + } + + void expect_add_snap(MockRefreshImageCtx &mock_image_ctx, + const std::string &snap_name, uint64_t snap_id) { + EXPECT_CALL(mock_image_ctx, add_snap(_, snap_name, snap_id, _, _, _, _, _)); + } + + void expect_init_exclusive_lock(MockRefreshImageCtx &mock_image_ctx, + MockExclusiveLock &mock_exclusive_lock, + int r) { + EXPECT_CALL(mock_image_ctx, create_exclusive_lock()) + .WillOnce(Return(&mock_exclusive_lock)); + EXPECT_CALL(mock_exclusive_lock, init(mock_image_ctx.features, _)) + .WillOnce(WithArg<1>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } + + void expect_shut_down_exclusive_lock(MockRefreshImageCtx &mock_image_ctx, + MockExclusiveLock &mock_exclusive_lock, + int r) { + EXPECT_CALL(mock_exclusive_lock, shut_down(_)) + .WillOnce(DoAll(ShutDownExclusiveLock(&mock_image_ctx), + CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } + + void expect_init_layout(MockRefreshImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, init_layout(_)); + } + + void expect_test_features(MockRefreshImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_, _)) + .WillRepeatedly(TestFeatures(&mock_image_ctx)); + } + + void expect_refresh_parent_is_required(MockRefreshParentRequest &mock_refresh_parent_request, + bool required) { + EXPECT_CALL(mock_refresh_parent_request, is_refresh_required()) + .WillRepeatedly(Return(required)); + } + + void expect_refresh_parent_send(MockRefreshImageCtx &mock_image_ctx, + MockRefreshParentRequest &mock_refresh_parent_request, + int r) { + EXPECT_CALL(mock_refresh_parent_request, send()) + .WillOnce(FinishRequest(&mock_refresh_parent_request, r, + &mock_image_ctx)); + } + + void expect_refresh_parent_apply(MockRefreshParentRequest &mock_refresh_parent_request) { + EXPECT_CALL(mock_refresh_parent_request, apply()); + } + + void expect_refresh_parent_finalize(MockRefreshImageCtx &mock_image_ctx, + MockRefreshParentRequest &mock_refresh_parent_request, + int r) { + EXPECT_CALL(mock_refresh_parent_request, finalize(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_is_exclusive_lock_owner(MockExclusiveLock &mock_exclusive_lock, + bool is_owner) { + EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillOnce(Return(is_owner)); + } + + void expect_get_journal_policy(MockImageCtx &mock_image_ctx, + MockJournalPolicy &mock_journal_policy) { + EXPECT_CALL(mock_image_ctx, get_journal_policy()) + .WillOnce(Return(&mock_journal_policy)); + } + + void expect_journal_disabled(MockJournalPolicy &mock_journal_policy, + bool disabled) { + EXPECT_CALL(mock_journal_policy, journal_disabled()) + .WillOnce(Return(disabled)); + } + + void expect_open_journal(MockRefreshImageCtx &mock_image_ctx, + MockJournal &mock_journal, int r) { + EXPECT_CALL(mock_image_ctx, create_journal()) + .WillOnce(Return(&mock_journal)); + EXPECT_CALL(mock_journal, open(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_close_journal(MockRefreshImageCtx &mock_image_ctx, + MockJournal &mock_journal, int r) { + EXPECT_CALL(mock_journal, close(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_open_object_map(MockRefreshImageCtx &mock_image_ctx, + MockObjectMap *mock_object_map, int r) { + EXPECT_CALL(mock_image_ctx, create_object_map(_)) + .WillOnce(Return(mock_object_map)); + EXPECT_CALL(*mock_object_map, open(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_close_object_map(MockRefreshImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map, int r) { + EXPECT_CALL(mock_object_map, close(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_get_snap_id(MockRefreshImageCtx &mock_image_ctx, + const std::string &snap_name, + uint64_t snap_id) { + EXPECT_CALL(mock_image_ctx, + get_snap_id(_, snap_name)).WillOnce(Return(snap_id)); + } + + void expect_block_writes(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, block_writes(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_unblock_writes(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, unblock_writes()) + .Times(1); + } + + void expect_image_flush(MockIoImageDispatchSpec &mock_image_request, int r) { + EXPECT_CALL(mock_image_request, send()) + .WillOnce(Invoke([&mock_image_request, r]() { + mock_image_request.aio_comp->set_request_count(1); + mock_image_request.aio_comp->add_request(); + mock_image_request.aio_comp->complete_request(r); + })); + } + +}; + +TEST_F(TestMockImageRefreshRequest, SuccessV1) { + REQUIRE_FORMAT_V1(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_v1_read_header(mock_image_ctx, 0); + expect_v1_get_snapshots(mock_image_ctx, 0); + expect_v1_get_locks(mock_image_ctx, 0); + expect_init_layout(mock_image_ctx); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessSnapshotV1) { + REQUIRE_FORMAT_V1(); + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_v1_read_header(mock_image_ctx, 0); + expect_v1_get_snapshots(mock_image_ctx, 0); + expect_v1_get_locks(mock_image_ctx, 0); + expect_init_layout(mock_image_ctx); + expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessV2) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, -EOPNOTSUPP); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessSnapshotV2) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_get_snapshots(mock_image_ctx, false, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessLegacySnapshotV2) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, -EOPNOTSUPP); + expect_get_parent_legacy(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_get_snapshots(mock_image_ctx, true, -EOPNOTSUPP); + expect_get_snapshots_legacy(mock_image_ctx, true, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessLegacySnapshotNoTimestampV2) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, -EOPNOTSUPP); + expect_get_parent_legacy(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_get_snapshots(mock_image_ctx, true, -EOPNOTSUPP); + expect_get_snapshots_legacy(mock_image_ctx, true, -EOPNOTSUPP); + expect_get_snapshots_legacy(mock_image_ctx, false, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + + +TEST_F(TestMockImageRefreshRequest, SuccessSetSnapshotV2) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx, + cls::rbd::UserSnapshotNamespace(), + "snap")); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockObjectMap mock_object_map; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_get_snapshots(mock_image_ctx, false, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + expect_open_object_map(mock_image_ctx, &mock_object_map, 0); + } + expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + expect_get_snap_id(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessChild) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + librbd::ImageCtx *ictx2 = nullptr; + std::string clone_name = get_temp_image_name(); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + ASSERT_EQ(0, snap_protect(*ictx, "snap")); + BOOST_SCOPE_EXIT_ALL((&)) { + if (ictx2 != nullptr) { + close_image(ictx2); + } + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name, no_op)); + ASSERT_EQ(0, ictx->operations->snap_unprotect(cls::rbd::UserSnapshotNamespace(), "snap")); + }; + + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap", m_ioctx, + clone_name.c_str(), ictx->features, &order, 0, 0)); + + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + + MockRefreshImageCtx mock_image_ctx(*ictx2); + MockRefreshParentRequest *mock_refresh_parent_request = new MockRefreshParentRequest(); + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx2->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(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); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + expect_refresh_parent_apply(*mock_refresh_parent_request); + expect_refresh_parent_finalize(mock_image_ctx, *mock_refresh_parent_request, 0); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessChildDontOpenParent) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + librbd::ImageCtx *ictx2 = nullptr; + std::string clone_name = get_temp_image_name(); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + ASSERT_EQ(0, snap_protect(*ictx, "snap")); + BOOST_SCOPE_EXIT_ALL((&)) { + if (ictx2 != nullptr) { + close_image(ictx2); + } + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name, no_op)); + ASSERT_EQ(0, ictx->operations->snap_unprotect(cls::rbd::UserSnapshotNamespace(), "snap")); + }; + + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap", m_ioctx, + clone_name.c_str(), ictx->features, &order, 0, 0)); + + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + + MockRefreshImageCtx mock_image_ctx(*ictx2); + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx2->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(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); + } + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, true, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessOpFeatures) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + mock_image_ctx.features |= RBD_FEATURE_OPERATIONS; + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, mock_image_ctx.features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_op_features(mock_image_ctx, 4096, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(4096U, mock_image_ctx.op_features); + ASSERT_TRUE(mock_image_ctx.operations_disabled); +} + +TEST_F(TestMockImageRefreshRequest, DisableExclusiveLock) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock *mock_exclusive_lock = new MockExclusiveLock(); + mock_image_ctx.exclusive_lock = mock_exclusive_lock; + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + MockJournal mock_journal; + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + mock_image_ctx.journal = &mock_journal; + } + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_OBJECT_MAP, + false)); + } + + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_EXCLUSIVE_LOCK, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + // verify that exclusive lock is properly handled when object map + // and journaling were never enabled (or active) + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_shut_down_exclusive_lock(mock_image_ctx, *mock_exclusive_lock, 0); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, DisableExclusiveLockWhileAcquiringLock) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_OBJECT_MAP, + false)); + } + + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_EXCLUSIVE_LOCK, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + // verify that exclusive lock is properly handled when object map + // and journaling were never enabled (or active) + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, true, false, &ctx); + req->send(); + + ASSERT_EQ(-ERESTART, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, JournalDisabledByPolicy) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_FAST_DIFF, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockJournal mock_journal; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, true); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + + MockJournalPolicy mock_journal_policy; + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_journal_disabled(mock_journal_policy, true); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, EnableJournalWithExclusiveLock) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_FAST_DIFF, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockJournal mock_journal; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, true); + + // journal should be immediately opened if exclusive lock owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + + MockJournalPolicy mock_journal_policy; + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_journal_disabled(mock_journal_policy, false); + expect_open_journal(mock_image_ctx, mock_journal, 0); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, EnableJournalWithoutExclusiveLock) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_OBJECT_MAP, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, false); + + // do not open the journal if exclusive lock is not owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_set_require_lock(mock_image_ctx, librbd::io::DIRECTION_BOTH, true); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, DisableJournal) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + MockJournal *mock_journal = new MockJournal(); + mock_image_ctx.journal = mock_journal; + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + // verify journal is closed if feature disabled + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_block_writes(mock_image_ctx, 0); + if (!mock_image_ctx.clone_copy_on_read) { + expect_set_require_lock(mock_image_ctx, librbd::io::DIRECTION_READ, false); + } + expect_close_journal(mock_image_ctx, *mock_journal, 0); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, EnableObjectMapWithExclusiveLock) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockObjectMap mock_object_map; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, true); + + // object map should be immediately opened if exclusive lock owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_open_object_map(mock_image_ctx, &mock_object_map, 0); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, EnableObjectMapWithoutExclusiveLock) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, false); + + // do not open the object map if exclusive lock is not owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, DisableObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockObjectMap *mock_object_map = new MockObjectMap(); + mock_image_ctx.object_map = mock_object_map; + + MockJournal mock_journal; + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + mock_image_ctx.journal = &mock_journal; + } + + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_FAST_DIFF, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + // verify object map is closed if feature disabled + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_close_object_map(mock_image_ctx, *mock_object_map, 0); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, OpenObjectMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockObjectMap *mock_object_map = new MockObjectMap(); + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, true); + + // object map should be immediately opened if exclusive lock owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_open_object_map(mock_image_ctx, mock_object_map, -EBLACKLISTED); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, + &ctx); + req->send(); + + ASSERT_EQ(-EBLACKLISTED, ctx.wait()); + ASSERT_EQ(nullptr, mock_image_ctx.object_map); +} + +TEST_F(TestMockImageRefreshRequest, OpenObjectMapTooLarge) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockObjectMap *mock_object_map = new MockObjectMap(); + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, true); + + // object map should be immediately opened if exclusive lock owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_open_object_map(mock_image_ctx, mock_object_map, -EFBIG); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(nullptr, mock_image_ctx.object_map); +} + +TEST_F(TestMockImageRefreshRequest, ApplyMetadataError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, 0); + expect_apply_metadata(mock_image_ctx, -EINVAL); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_RemoveRequest.cc b/src/test/librbd/image/test_mock_RemoveRequest.cc new file mode 100644 index 00000000..05f4a546 --- /dev/null +++ b/src/test/librbd/image/test_mock_RemoveRequest.cc @@ -0,0 +1,468 @@ +// -*- 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 "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/image/TypeTraits.h" +#include "librbd/image/DetachChildRequest.h" +#include "librbd/image/PreRemoveRequest.h" +#include "librbd/image/RemoveRequest.h" +#include "librbd/journal/RemoveRequest.h" +#include "librbd/mirror/DisableRequest.h" +#include "librbd/operation/TrimRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <list> +#include <boost/scope_exit.hpp> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + static MockTestImageCtx* s_instance; + static MockTestImageCtx* create(const std::string &image_name, + const std::string &image_id, + const char *snap, librados::IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + s_instance = this; + } +}; + +MockTestImageCtx* MockTestImageCtx::s_instance = nullptr; + +} // anonymous namespace + +namespace image { + +template <> +struct TypeTraits<MockTestImageCtx> { + typedef librbd::MockContextWQ ContextWQ; +}; + +template <> +class DetachChildRequest<MockTestImageCtx> { +public: + static DetachChildRequest *s_instance; + static DetachChildRequest *create(MockTestImageCtx &image_ctx, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + DetachChildRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +DetachChildRequest<MockTestImageCtx> *DetachChildRequest<MockTestImageCtx>::s_instance; + +template <> +class PreRemoveRequest<MockTestImageCtx> { +public: + static PreRemoveRequest *s_instance; + static PreRemoveRequest *create(MockTestImageCtx* image_ctx, bool force, + Context* on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + PreRemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +PreRemoveRequest<MockTestImageCtx> *PreRemoveRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace image + +namespace operation { + +template <> +class TrimRequest<MockTestImageCtx> { +public: + static TrimRequest *s_instance; + static TrimRequest *create(MockTestImageCtx &image_ctx, Context *on_finish, + uint64_t original_size, uint64_t new_size, + ProgressContext &prog_ctx) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + TrimRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +TrimRequest<MockTestImageCtx> *TrimRequest<MockTestImageCtx>::s_instance; + +} // namespace operation + +namespace journal { + +template <> +class RemoveRequest<MockTestImageCtx> { +private: + typedef ::librbd::image::TypeTraits<MockTestImageCtx> TypeTraits; + typedef typename TypeTraits::ContextWQ ContextWQ; +public: + static RemoveRequest *s_instance; + static RemoveRequest *create(IoCtx &ioctx, const std::string &imageid, + const std::string &client_id, + ContextWQ *op_work_queue, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + RemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +RemoveRequest<MockTestImageCtx> *RemoveRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace journal + +namespace mirror { + +template<> +class DisableRequest<MockTestImageCtx> { +public: + static DisableRequest *s_instance; + Context *on_finish = nullptr; + + static DisableRequest *create(MockTestImageCtx *image_ctx, bool force, + bool remove, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + DisableRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +DisableRequest<MockTestImageCtx> *DisableRequest<MockTestImageCtx>::s_instance; + +} // namespace mirror +} // namespace librbd + +// template definitions +#include "librbd/image/RemoveRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::SetArgPointee; +using ::testing::StrEq; + +class TestMockImageRemoveRequest : public TestMockFixture { +public: + typedef ::librbd::image::TypeTraits<MockTestImageCtx> TypeTraits; + typedef typename TypeTraits::ContextWQ ContextWQ; + typedef RemoveRequest<MockTestImageCtx> MockRemoveRequest; + typedef PreRemoveRequest<MockTestImageCtx> MockPreRemoveRequest; + typedef DetachChildRequest<MockTestImageCtx> MockDetachChildRequest; + typedef librbd::operation::TrimRequest<MockTestImageCtx> MockTrimRequest; + typedef librbd::journal::RemoveRequest<MockTestImageCtx> MockJournalRemoveRequest; + typedef librbd::mirror::DisableRequest<MockTestImageCtx> MockMirrorDisableRequest; + + librbd::ImageCtx *m_test_imctx = NULL; + MockTestImageCtx *m_mock_imctx = NULL; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_test_imctx)); + m_mock_imctx = new MockTestImageCtx(*m_test_imctx); + librbd::MockTestImageCtx::s_instance = m_mock_imctx; + } + void TearDown() override { + librbd::MockTestImageCtx::s_instance = NULL; + delete m_mock_imctx; + TestMockFixture::TearDown(); + } + + void expect_state_open(MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, open(_, _)) + .WillOnce(Invoke([r](bool open_parent, Context *on_ready) { + on_ready->complete(r); + })); + if (r < 0) { + EXPECT_CALL(mock_image_ctx, destroy()); + } + } + + void expect_state_close(MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, close(_)) + .WillOnce(Invoke([](Context *on_ready) { + on_ready->complete(0); + })); + EXPECT_CALL(mock_image_ctx, destroy()); + } + + void expect_wq_queue(ContextWQ &wq, int r) { + EXPECT_CALL(wq, queue(_, r)) + .WillRepeatedly(Invoke([](Context *on_ready, int r) { + on_ready->complete(r); + })); + } + + void expect_pre_remove_image(MockTestImageCtx &mock_image_ctx, + MockPreRemoveRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(FinishRequest(&mock_request, r, &mock_image_ctx)); + } + + void expect_trim(MockTestImageCtx &mock_image_ctx, + MockTrimRequest &mock_trim_request, int r) { + EXPECT_CALL(mock_trim_request, send()) + .WillOnce(FinishRequest(&mock_trim_request, r, &mock_image_ctx)); + } + + void expect_journal_remove(MockTestImageCtx &mock_image_ctx, + MockJournalRemoveRequest &mock_journal_remove_request, int r) { + EXPECT_CALL(mock_journal_remove_request, send()) + .WillOnce(FinishRequest(&mock_journal_remove_request, r, &mock_image_ctx)); + } + + void expect_mirror_disable(MockTestImageCtx &mock_image_ctx, + MockMirrorDisableRequest &mock_mirror_disable_request, int r) { + EXPECT_CALL(mock_mirror_disable_request, send()) + .WillOnce(FinishRequest(&mock_mirror_disable_request, r, &mock_image_ctx)); + } + + void expect_remove_mirror_image(librados::IoCtx &ioctx, int r) { + EXPECT_CALL(get_mock_io_ctx(ioctx), + exec(StrEq("rbd_mirroring"), _, StrEq("rbd"), StrEq("mirror_image_remove"), + _, _, _)) + .WillOnce(Return(r)); + } + + void expect_dir_remove_image(librados::IoCtx &ioctx, int r) { + EXPECT_CALL(get_mock_io_ctx(ioctx), + exec(RBD_DIRECTORY, _, StrEq("rbd"), StrEq("dir_remove_image"), + _, _, _)) + .WillOnce(Return(r)); + } + + void expect_detach_child(MockTestImageCtx &mock_image_ctx, + MockDetachChildRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(FinishRequest(&mock_request, r, &mock_image_ctx)); + } +}; + +TEST_F(TestMockImageRemoveRequest, SuccessV1) { + REQUIRE_FORMAT_V1(); + expect_op_work_queue(*m_mock_imctx); + + InSequence seq; + expect_state_open(*m_mock_imctx, 0); + + MockPreRemoveRequest mock_pre_remove_request; + expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0); + + MockTrimRequest mock_trim_request; + expect_trim(*m_mock_imctx, mock_trim_request, 0); + + expect_state_close(*m_mock_imctx); + + ContextWQ op_work_queue; + expect_wq_queue(op_work_queue, 0); + + C_SaferCond ctx; + librbd::NoOpProgressContext no_op; + MockRemoveRequest *req = MockRemoveRequest::create(m_ioctx, m_image_name, "", + true, false, no_op, &op_work_queue, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRemoveRequest, OpenFailV1) { + REQUIRE_FORMAT_V1(); + + InSequence seq; + expect_state_open(*m_mock_imctx, -ENOENT); + + ContextWQ op_work_queue; + expect_wq_queue(op_work_queue, 0); + + C_SaferCond ctx; + librbd::NoOpProgressContext no_op; + MockRemoveRequest *req = MockRemoveRequest::create(m_ioctx, m_image_name, "", + true, false, no_op, &op_work_queue, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRemoveRequest, SuccessV2CloneV1) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + expect_op_work_queue(*m_mock_imctx); + + m_mock_imctx->parent_md.spec.pool_id = m_ioctx.get_id(); + m_mock_imctx->parent_md.spec.image_id = "parent id"; + m_mock_imctx->parent_md.spec.snap_id = 234; + + InSequence seq; + expect_state_open(*m_mock_imctx, 0); + + MockPreRemoveRequest mock_pre_remove_request; + expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0); + + MockTrimRequest mock_trim_request; + expect_trim(*m_mock_imctx, mock_trim_request, 0); + + MockDetachChildRequest mock_detach_child_request; + expect_detach_child(*m_mock_imctx, mock_detach_child_request, 0); + + MockMirrorDisableRequest mock_mirror_disable_request; + expect_mirror_disable(*m_mock_imctx, mock_mirror_disable_request, 0); + + expect_state_close(*m_mock_imctx); + + MockJournalRemoveRequest mock_journal_remove_request; + expect_journal_remove(*m_mock_imctx, mock_journal_remove_request, 0); + + expect_remove_mirror_image(m_ioctx, 0); + expect_dir_remove_image(m_ioctx, 0); + + C_SaferCond ctx; + librbd::NoOpProgressContext no_op; + ContextWQ op_work_queue; + MockRemoveRequest *req = MockRemoveRequest::create( + m_ioctx, m_image_name, "", true, false, no_op, &op_work_queue, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRemoveRequest, SuccessV2CloneV2) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + expect_op_work_queue(*m_mock_imctx); + + m_mock_imctx->parent_md.spec.pool_id = m_ioctx.get_id(); + m_mock_imctx->parent_md.spec.image_id = "parent id"; + m_mock_imctx->parent_md.spec.snap_id = 234; + + InSequence seq; + expect_state_open(*m_mock_imctx, 0); + + MockPreRemoveRequest mock_pre_remove_request; + expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0); + + MockTrimRequest mock_trim_request; + expect_trim(*m_mock_imctx, mock_trim_request, 0); + + MockDetachChildRequest mock_detach_child_request; + expect_detach_child(*m_mock_imctx, mock_detach_child_request, 0); + + MockMirrorDisableRequest mock_mirror_disable_request; + expect_mirror_disable(*m_mock_imctx, mock_mirror_disable_request, 0); + + expect_state_close(*m_mock_imctx); + + MockJournalRemoveRequest mock_journal_remove_request; + expect_journal_remove(*m_mock_imctx, mock_journal_remove_request, 0); + + expect_remove_mirror_image(m_ioctx, 0); + expect_dir_remove_image(m_ioctx, 0); + + C_SaferCond ctx; + librbd::NoOpProgressContext no_op; + ContextWQ op_work_queue; + MockRemoveRequest *req = MockRemoveRequest::create( + m_ioctx, m_image_name, "", true, false, no_op, &op_work_queue, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRemoveRequest, NotExistsV2) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + expect_op_work_queue(*m_mock_imctx); + + m_mock_imctx->parent_md.spec.pool_id = m_ioctx.get_id(); + m_mock_imctx->parent_md.spec.image_id = "parent id"; + m_mock_imctx->parent_md.spec.snap_id = 234; + + InSequence seq; + expect_state_open(*m_mock_imctx, 0); + + MockPreRemoveRequest mock_pre_remove_request; + expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0); + + MockTrimRequest mock_trim_request; + expect_trim(*m_mock_imctx, mock_trim_request, 0); + + MockDetachChildRequest mock_detach_child_request; + expect_detach_child(*m_mock_imctx, mock_detach_child_request, 0); + + MockMirrorDisableRequest mock_mirror_disable_request; + expect_mirror_disable(*m_mock_imctx, mock_mirror_disable_request, 0); + + expect_state_close(*m_mock_imctx); + + MockJournalRemoveRequest mock_journal_remove_request; + expect_journal_remove(*m_mock_imctx, mock_journal_remove_request, 0); + + expect_remove_mirror_image(m_ioctx, 0); + expect_dir_remove_image(m_ioctx, -ENOENT); + + C_SaferCond ctx; + librbd::NoOpProgressContext no_op; + ContextWQ op_work_queue; + MockRemoveRequest *req = MockRemoveRequest::create( + m_ioctx, m_image_name, "", true, false, no_op, &op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_ValidatePoolRequest.cc b/src/test/librbd/image/test_mock_ValidatePoolRequest.cc new file mode 100644 index 00000000..e40e50af --- /dev/null +++ b/src/test/librbd/image/test_mock_ValidatePoolRequest.cc @@ -0,0 +1,229 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.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 librbd + +// template definitions +#include "librbd/image/ValidatePoolRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockImageValidatePoolRequest : public TestMockFixture { +public: + typedef ValidatePoolRequest<MockTestImageCtx> MockValidatePoolRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); + } + + void expect_clone(librados::MockTestMemIoCtxImpl &mock_io_ctx) { + EXPECT_CALL(mock_io_ctx, clone()) + .WillOnce(Invoke([&mock_io_ctx]() { + mock_io_ctx.get(); + return &mock_io_ctx; + })); + } + + void expect_read_rbd_info(librados::MockTestMemIoCtxImpl &mock_io_ctx, + const std::string& data, int r) { + auto& expect = EXPECT_CALL(mock_io_ctx, read(StrEq(RBD_INFO), 0, 0, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(WithArg<3>(Invoke([data](bufferlist* bl) { + bl->append(data); + return 0; + }))); + } + } + + void expect_write_rbd_info(librados::MockTestMemIoCtxImpl &mock_io_ctx, + const std::string& data, int r) { + bufferlist bl; + bl.append(data); + + EXPECT_CALL(mock_io_ctx, write(StrEq(RBD_INFO), ContentsEqual(bl), + data.length(), 0, _)) + .WillOnce(Return(r)); + } + + void expect_allocate_snap_id(librados::MockTestMemIoCtxImpl &mock_io_ctx, + int r) { + auto &expect = EXPECT_CALL(mock_io_ctx, selfmanaged_snap_create(_)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_release_snap_id(librados::MockTestMemIoCtxImpl &mock_io_ctx, + int r) { + auto &expect = EXPECT_CALL(mock_io_ctx, selfmanaged_snap_remove(_)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + librbd::ImageCtx *image_ctx; +}; + +TEST_F(TestMockImageValidatePoolRequest, Success) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", -ENOENT); + expect_allocate_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "validate", 0); + expect_release_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "overwrite validated", 0); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, AlreadyValidated) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "overwrite validated", 0); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, SnapshotsValidated) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "validate", 0); + expect_write_rbd_info(mock_io_ctx, "overwrite validated", 0); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, ReadError) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", -EPERM); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, CreateSnapshotError) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", 0); + expect_allocate_snap_id(mock_io_ctx, -EPERM); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, WriteError) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", -ENOENT); + expect_allocate_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "validate", -EPERM); + expect_release_snap_id(mock_io_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, RemoveSnapshotError) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", -ENOENT); + expect_allocate_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "validate", 0); + expect_release_snap_id(mock_io_ctx, -EPERM); + expect_write_rbd_info(mock_io_ctx, "overwrite validated", 0); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, OverwriteError) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", -ENOENT); + expect_allocate_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "validate", 0); + expect_release_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "overwrite validated", -EOPNOTSUPP); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/io/test_mock_CopyupRequest.cc b/src/test/librbd/io/test_mock_CopyupRequest.cc new file mode 100644 index 00000000..7a30f59a --- /dev/null +++ b/src/test/librbd/io/test_mock_CopyupRequest.cc @@ -0,0 +1,1068 @@ +// -*- 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/MockExclusiveLock.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockJournal.h" +#include "test/librbd/mock/MockObjectMap.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "include/rbd/librbd.hpp" +#include "librbd/deep_copy/ObjectCopyRequest.h" +#include "librbd/io/CopyupRequest.h" +#include "librbd/io/ImageRequest.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/io/ObjectRequest.h" +#include "librbd/io/ReadResult.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx, + MockTestImageCtx* mock_parent_image_ctx = nullptr) + : MockImageCtx(image_ctx) { + parent = mock_parent_image_ctx; + } + + std::map<uint64_t, librbd::io::CopyupRequest<librbd::MockTestImageCtx>*> copyup_list; +}; + +} // anonymous namespace + +namespace util { + +inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util + +namespace deep_copy { + +template <> +struct ObjectCopyRequest<librbd::MockTestImageCtx> { + static ObjectCopyRequest* s_instance; + static ObjectCopyRequest* create(librbd::MockImageCtx* parent_image_ctx, + librbd::MockTestImageCtx* image_ctx, + librados::snap_t src_snap_id_start, + librados::snap_t dst_snap_id_start, + const SnapMap &snap_map, + uint64_t object_number, bool flatten, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->object_number = object_number; + s_instance->flatten = flatten; + s_instance->on_finish = on_finish; + return s_instance; + } + + uint64_t object_number; + bool flatten; + Context *on_finish; + + ObjectCopyRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +ObjectCopyRequest<librbd::MockTestImageCtx>* ObjectCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace deep_copy + +namespace io { + +template <> +struct ObjectRequest<librbd::MockTestImageCtx> { + static void add_write_hint(librbd::MockTestImageCtx&, + librados::ObjectWriteOperation*) { + } +}; + +template <> +struct AbstractObjectWriteRequest<librbd::MockTestImageCtx> { + C_SaferCond ctx; + void handle_copyup(int r) { + ctx.complete(r); + } + + MOCK_CONST_METHOD0(get_pre_write_object_map_state, uint8_t()); + MOCK_CONST_METHOD0(is_empty_write_op, bool()); + + MOCK_METHOD1(add_copyup_ops, void(librados::ObjectWriteOperation*)); +}; + +template <> +struct ImageRequest<librbd::MockTestImageCtx> { + static ImageRequest *s_instance; + static void aio_read(librbd::MockImageCtx *ictx, AioCompletion *c, + Extents &&image_extents, ReadResult &&read_result, + int op_flags, const ZTracer::Trace &parent_trace) { + s_instance->aio_read(c, image_extents, &read_result); + } + + MOCK_METHOD3(aio_read, void(AioCompletion *, const Extents&, ReadResult*)); + + ImageRequest() { + s_instance = this; + } +}; + +ImageRequest<librbd::MockTestImageCtx>* ImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace io +} // namespace librbd + +static bool operator==(const SnapContext& rhs, const SnapContext& lhs) { + return (rhs.seq == lhs.seq && rhs.snaps == lhs.snaps); +} + +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/io/CopyupRequest.cc" + +namespace librbd { +namespace io { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; +using ::testing::WithArgs; +using ::testing::WithoutArgs; + +struct TestMockIoCopyupRequest : public TestMockFixture { + typedef CopyupRequest<librbd::MockTestImageCtx> MockCopyupRequest; + typedef ImageRequest<librbd::MockTestImageCtx> MockImageRequest; + typedef ObjectRequest<librbd::MockTestImageCtx> MockObjectRequest; + typedef AbstractObjectWriteRequest<librbd::MockTestImageCtx> MockAbstractObjectWriteRequest; + typedef deep_copy::ObjectCopyRequest<librbd::MockTestImageCtx> MockObjectCopyRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + if (!is_feature_enabled(RBD_FEATURE_LAYERING)) { + return; + } + + m_parent_image_name = m_image_name; + m_image_name = get_temp_image_name(); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_parent_image_name.c_str(), + nullptr)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + + uint64_t features; + ASSERT_EQ(0, image.features(&features)); + image.close(); + + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_parent_image_name.c_str(), "one", m_ioctx, + m_image_name.c_str(), features, &order)); + } + + void expect_get_parent_overlap(MockTestImageCtx& mock_image_ctx, + librados::snap_t snap_id, uint64_t overlap, + int r) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(mock_image_ctx, get_parent_overlap(snap_id, _)) + .WillOnce(WithArg<1>(Invoke([overlap, r](uint64_t *o) { + *o = overlap; + return r; + }))); + } + } + + void expect_prune_parent_extents(MockTestImageCtx& mock_image_ctx, + uint64_t overlap, uint64_t object_overlap) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, overlap)) + .WillOnce(WithoutArgs(Invoke([object_overlap]() { + return object_overlap; + }))); + } + } + + void expect_read_parent(MockTestImageCtx& mock_image_ctx, + MockImageRequest& mock_image_request, + const Extents& image_extents, + const std::string& data, int r) { + EXPECT_CALL(mock_image_request, aio_read(_, image_extents, _)) + .WillOnce(WithArgs<0, 2>(Invoke( + [&mock_image_ctx, image_extents, data, r]( + AioCompletion* aio_comp, ReadResult* read_result) { + aio_comp->read_result = std::move(*read_result); + aio_comp->set_request_count(1); + auto ctx = new ReadResult::C_ImageReadRequest(aio_comp, + image_extents); + ctx->bl.append(data); + mock_image_ctx.image_ctx->op_work_queue->queue(ctx, r); + }))); + } + + void expect_copyup(MockTestImageCtx& mock_image_ctx, uint64_t snap_id, + const std::string& oid, const std::string& data, int r) { + bufferlist in_bl; + in_bl.append(data); + + SnapContext snapc; + if (snap_id == CEPH_NOSNAP) { + snapc = mock_image_ctx.snapc; + } + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + exec(oid, _, StrEq("rbd"), StrEq("copyup"), + ContentsEqual(in_bl), _, snapc)) + .WillOnce(Return(r)); + } + + void expect_write(MockTestImageCtx& mock_image_ctx, uint64_t snap_id, + const std::string& oid, int r) { + SnapContext snapc; + if (snap_id == CEPH_NOSNAP) { + snapc = mock_image_ctx.snapc; + } + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + write(oid, _, 0, 0, snapc)) + .WillOnce(Return(r)); + } + + void expect_test_features(MockTestImageCtx& mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_, _)) + .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) { + return (mock_image_ctx.features & features) != 0; + }))); + } + + void expect_is_lock_owner(MockTestImageCtx& mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, + is_lock_owner()).WillRepeatedly(Return(true)); + } + } + + void expect_is_empty_write_op(MockAbstractObjectWriteRequest& mock_write_request, + bool is_empty) { + EXPECT_CALL(mock_write_request, is_empty_write_op()) + .WillOnce(Return(is_empty)); + } + + void expect_add_copyup_ops(MockAbstractObjectWriteRequest& mock_write_request) { + EXPECT_CALL(mock_write_request, add_copyup_ops(_)) + .WillOnce(Invoke([](librados::ObjectWriteOperation* op) { + op->write(0, bufferlist{}); + })); + } + + void expect_get_pre_write_object_map_state(MockTestImageCtx& mock_image_ctx, + MockAbstractObjectWriteRequest& mock_write_request, + uint8_t state) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(mock_write_request, get_pre_write_object_map_state()) + .WillOnce(Return(state)); + } + } + + void expect_object_map_at(MockTestImageCtx& mock_image_ctx, + uint64_t object_no, uint8_t state) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, at(object_no)) + .WillOnce(Return(state)); + } + } + + void expect_object_map_update(MockTestImageCtx& mock_image_ctx, + uint64_t snap_id, uint64_t object_no, + uint8_t state, bool updated, int ret_val) { + if (mock_image_ctx.object_map != nullptr) { + if (!mock_image_ctx.image_ctx->test_features(RBD_FEATURE_FAST_DIFF) && + state == OBJECT_EXISTS_CLEAN) { + state = OBJECT_EXISTS; + } + + EXPECT_CALL(*mock_image_ctx.object_map, + aio_update(snap_id, object_no, object_no + 1, state, + boost::optional<uint8_t>(), _, + (snap_id != CEPH_NOSNAP), _)) + .WillOnce(WithArg<7>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) { + if (updated) { + mock_image_ctx.op_work_queue->queue(ctx, ret_val); + } + return updated; + }))); + } + } + + void expect_object_copy(MockTestImageCtx& mock_image_ctx, + MockObjectCopyRequest& mock_object_copy_request, + bool flatten, int r) { + EXPECT_CALL(mock_object_copy_request, send()) + .WillOnce(Invoke( + [&mock_image_ctx, &mock_object_copy_request, flatten, r]() { + ASSERT_EQ(flatten, mock_object_copy_request.flatten); + mock_image_ctx.op_work_queue->queue( + mock_object_copy_request.on_finish, r); + })); + } + + void flush_async_operations(librbd::ImageCtx* ictx) { + ictx->io_work_queue->flush(); + } + + std::string m_parent_image_name; +}; + +TEST_F(TestMockIoCopyupRequest, Standard) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + MockAbstractObjectWriteRequest mock_write_request; + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, StandardWithSnaps) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->snap_lock.get_write(); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "2", 2, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->snapc = {2, {2, 1}}; + ictx->snap_lock.put_write(); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_test_features(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + MockAbstractObjectWriteRequest mock_write_request; + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0); + expect_object_map_update(mock_image_ctx, 2, 0, OBJECT_EXISTS_CLEAN, true, 0); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, 0, "oid", data, 0); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, CopyOnRead) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->send(); + flush_async_operations(ictx); +} + +TEST_F(TestMockIoCopyupRequest, CopyOnReadWithSnaps) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->snap_lock.get_write(); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->snapc = {1, {1}}; + ictx->snap_lock.put_write(); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_test_features(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS_CLEAN, + true, 0); + + expect_copyup(mock_image_ctx, 0, "oid", data, 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->send(); + flush_async_operations(ictx); +} + +TEST_F(TestMockIoCopyupRequest, DeepCopy) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockAbstractObjectWriteRequest mock_write_request; + MockObjectCopyRequest mock_object_copy_request; + mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, true}; + expect_is_empty_write_op(mock_write_request, false); + expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0); + + expect_is_empty_write_op(mock_write_request, false); + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, DeepCopyOnRead) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockObjectCopyRequest mock_object_copy_request; + mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, + false}; + expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0); + + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->send(); + flush_async_operations(ictx); +} + +TEST_F(TestMockIoCopyupRequest, DeepCopyWithPostSnaps) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->snap_lock.get_write(); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "3", 3, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "2", 2, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->snapc = {3, {3, 2, 1}}; + ictx->snap_lock.put_write(); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_test_features(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockAbstractObjectWriteRequest mock_write_request; + MockObjectCopyRequest mock_object_copy_request; + mock_image_ctx.migration_info = {1, "", "", "image id", + {{CEPH_NOSNAP, {2, 1}}}, + ictx->size, true}; + expect_is_empty_write_op(mock_write_request, false); + expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0); + + expect_is_empty_write_op(mock_write_request, false); + expect_get_parent_overlap(mock_image_ctx, 1, 0, 0); + expect_get_parent_overlap(mock_image_ctx, 2, 1, 0); + expect_prune_parent_extents(mock_image_ctx, 1, 1); + expect_get_parent_overlap(mock_image_ctx, 3, 1, 0); + expect_prune_parent_extents(mock_image_ctx, 1, 1); + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, 2, 0, OBJECT_EXISTS, true, 0); + expect_object_map_update(mock_image_ctx, 3, 0, OBJECT_EXISTS_CLEAN, true, 0); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, DeepCopyWithPreAndPostSnaps) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->snap_lock.get_write(); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "4", 4, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "3", 3, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "2", 2, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->snapc = {4, {4, 3, 2, 1}}; + ictx->snap_lock.put_write(); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_test_features(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockAbstractObjectWriteRequest mock_write_request; + MockObjectCopyRequest mock_object_copy_request; + mock_image_ctx.migration_info = {1, "", "", "image id", + {{CEPH_NOSNAP, {2, 1}}, {10, {1}}}, + ictx->size, true}; + expect_is_empty_write_op(mock_write_request, false); + expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0); + + expect_is_empty_write_op(mock_write_request, false); + expect_get_parent_overlap(mock_image_ctx, 2, 0, 0); + expect_get_parent_overlap(mock_image_ctx, 3, 1, 0); + expect_prune_parent_extents(mock_image_ctx, 1, 1); + expect_get_parent_overlap(mock_image_ctx, 4, 1, 0); + expect_prune_parent_extents(mock_image_ctx, 1, 1); + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, 3, 0, OBJECT_EXISTS_CLEAN, true, 0); + expect_object_map_update(mock_image_ctx, 4, 0, OBJECT_EXISTS_CLEAN, true, 0); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, ZeroedCopyup) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockAbstractObjectWriteRequest mock_write_request; + expect_is_empty_write_op(mock_write_request, false); + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, ZeroedCopyOnRead) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '\0'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->send(); + flush_async_operations(ictx); +} + +TEST_F(TestMockIoCopyupRequest, NoOpCopyup) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + "", -ENOENT); + + MockAbstractObjectWriteRequest mock_write_request; + expect_is_empty_write_op(mock_write_request, true); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, RestartWrite) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + MockAbstractObjectWriteRequest mock_write_request1; + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request1, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + expect_add_copyup_ops(mock_write_request1); + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0); + + MockAbstractObjectWriteRequest mock_write_request2; + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + write("oid", _, 0, 0, _)) + .WillOnce(WithoutArgs(Invoke([req, &mock_write_request2]() { + req->append_request(&mock_write_request2); + return 0; + }))); + + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request1); + req->send(); + + ASSERT_EQ(0, mock_write_request1.ctx.wait()); + ASSERT_EQ(-ERESTART, mock_write_request2.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, ReadFromParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + "", -EPERM); + + MockAbstractObjectWriteRequest mock_write_request; + expect_is_empty_write_op(mock_write_request, false); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(-EPERM, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, DeepCopyError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockAbstractObjectWriteRequest mock_write_request; + MockObjectCopyRequest mock_object_copy_request; + mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, true}; + expect_is_empty_write_op(mock_write_request, false); + expect_object_copy(mock_image_ctx, mock_object_copy_request, true, -EPERM); + + expect_is_empty_write_op(mock_write_request, false); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(-EPERM, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, UpdateObjectMapError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + MockAbstractObjectWriteRequest mock_write_request; + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + -EINVAL); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(-EINVAL, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, CopyupError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->snap_lock.get_write(); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->snapc = {1, {1}}; + ictx->snap_lock.put_write(); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_test_features(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + MockAbstractObjectWriteRequest mock_write_request; + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, 0, "oid", data, -EPERM); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(-EPERM, mock_write_request.ctx.wait()); + flush_async_operations(ictx); +} + +} // namespace io +} // namespace librbd diff --git a/src/test/librbd/io/test_mock_ImageRequest.cc b/src/test/librbd/io/test_mock_ImageRequest.cc new file mode 100644 index 00000000..03405802 --- /dev/null +++ b/src/test/librbd/io/test_mock_ImageRequest.cc @@ -0,0 +1,468 @@ +// -*- 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/MockJournal.h" +#include "test/librbd/mock/cache/MockImageCache.h" +#include "librbd/io/ImageRequest.h" +#include "librbd/io/ObjectDispatchSpec.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx; + +struct MockTestJournal : public MockJournal { + MOCK_METHOD4(append_write_event, uint64_t(uint64_t, size_t, + const bufferlist &, bool)); + MOCK_METHOD5(append_io_event_mock, uint64_t(const journal::EventEntry&, + uint64_t, size_t, bool, int)); + uint64_t append_io_event(journal::EventEntry &&event_entry, + uint64_t offset, size_t length, + bool flush_entry, int filter_ret_val) { + // googlemock doesn't support move semantics + return append_io_event_mock(event_entry, offset, length, flush_entry, + filter_ret_val); + } + + MOCK_METHOD2(commit_io_event, void(uint64_t, int)); +}; + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } + + MockTestJournal* journal; +}; + +} // anonymous namespace + +namespace util { + +inline ImageCtx *get_image_ctx(MockTestImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util +} // namespace librbd + +#include "librbd/io/ImageRequest.cc" + +namespace librbd { +namespace io { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::WithoutArgs; +using ::testing::Exactly; + +struct TestMockIoImageRequest : public TestMockFixture { + typedef ImageRequest<librbd::MockTestImageCtx> MockImageRequest; + typedef ImageReadRequest<librbd::MockTestImageCtx> MockImageReadRequest; + typedef ImageWriteRequest<librbd::MockTestImageCtx> MockImageWriteRequest; + typedef ImageDiscardRequest<librbd::MockTestImageCtx> MockImageDiscardRequest; + typedef ImageFlushRequest<librbd::MockTestImageCtx> MockImageFlushRequest; + typedef ImageWriteSameRequest<librbd::MockTestImageCtx> MockImageWriteSameRequest; + typedef ImageCompareAndWriteRequest<librbd::MockTestImageCtx> MockImageCompareAndWriteRequest; + + void expect_is_journal_appending(MockTestJournal &mock_journal, bool appending) { + EXPECT_CALL(mock_journal, is_journal_appending()) + .WillOnce(Return(appending)); + } + + void expect_get_modify_timestamp(MockTestImageCtx &mock_image_ctx, + bool needs_update) { + if (needs_update) { + mock_image_ctx.mtime_update_interval = 5; + EXPECT_CALL(mock_image_ctx, get_modify_timestamp()) + .WillOnce(Return(ceph_clock_now() - utime_t(10,0))); + } else { + mock_image_ctx.mtime_update_interval = 600; + EXPECT_CALL(mock_image_ctx, get_modify_timestamp()) + .WillOnce(Return(ceph_clock_now())); + } + } + + void expect_object_discard_request(MockTestImageCtx &mock_image_ctx, + uint64_t object_no, uint64_t offset, + uint32_t length, int r) { + EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, send(_)) + .WillOnce(Invoke([&mock_image_ctx, object_no, offset, length, r] + (ObjectDispatchSpec* spec) { + auto* discard_spec = boost::get<ObjectDispatchSpec::DiscardRequest>(&spec->request); + ASSERT_TRUE(discard_spec != nullptr); + ASSERT_EQ(object_no, discard_spec->object_no); + ASSERT_EQ(offset, discard_spec->object_off); + ASSERT_EQ(length, discard_spec->object_len); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + mock_image_ctx.image_ctx->op_work_queue->queue(&spec->dispatcher_ctx, r); + })); + } + + void expect_object_request_send(MockTestImageCtx &mock_image_ctx, + int r) { + EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, send(_)) + .WillOnce(Invoke([&mock_image_ctx, r](ObjectDispatchSpec* spec) { + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + mock_image_ctx.image_ctx->op_work_queue->queue(&spec->dispatcher_ctx, r); + })); + } +}; + +TEST_F(TestMockIoImageRequest, AioWriteModifyTimestamp) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockTestJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + mock_image_ctx.mtime_update_interval = 5; + + utime_t dummy = ceph_clock_now(); + dummy -= utime_t(10,0); + + EXPECT_CALL(mock_image_ctx, get_modify_timestamp()) + .Times(Exactly(3)) + .WillOnce(Return(dummy)) + .WillOnce(Return(dummy)) + .WillOnce(Return(dummy + utime_t(10,0))); + + EXPECT_CALL(mock_image_ctx, set_modify_timestamp(_)) + .Times(Exactly(1)); + + InSequence seq; + expect_is_journal_appending(mock_journal, false); + expect_object_request_send(mock_image_ctx, 0); + + C_SaferCond aio_comp_ctx_1, aio_comp_ctx_2; + AioCompletion *aio_comp_1 = AioCompletion::create_and_start( + &aio_comp_ctx_1, ictx, AIO_TYPE_WRITE); + + AioCompletion *aio_comp_2 = AioCompletion::create_and_start( + &aio_comp_ctx_2, ictx, AIO_TYPE_WRITE); + + bufferlist bl; + bl.append("1"); + MockImageWriteRequest mock_aio_image_write_1(mock_image_ctx, aio_comp_1, + {{0, 1}}, std::move(bl), 0, {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_write_1.send(); + } + ASSERT_EQ(0, aio_comp_ctx_1.wait()); + + expect_is_journal_appending(mock_journal, false); + expect_object_request_send(mock_image_ctx, 0); + + bl.append("1"); + MockImageWriteRequest mock_aio_image_write_2(mock_image_ctx, aio_comp_2, + {{0, 1}}, std::move(bl), 0, {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_write_2.send(); + } + ASSERT_EQ(0, aio_comp_ctx_2.wait()); +} + +TEST_F(TestMockIoImageRequest, AioReadAccessTimestamp) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockTestJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + mock_image_ctx.atime_update_interval = 5; + + utime_t dummy = ceph_clock_now(); + dummy -= utime_t(10,0); + + EXPECT_CALL(mock_image_ctx, get_access_timestamp()) + .Times(Exactly(3)) + .WillOnce(Return(dummy)) + .WillOnce(Return(dummy)) + .WillOnce(Return(dummy + utime_t(10,0))); + + EXPECT_CALL(mock_image_ctx, set_access_timestamp(_)) + .Times(Exactly(1)); + + InSequence seq; + expect_object_request_send(mock_image_ctx, 0); + + C_SaferCond aio_comp_ctx_1, aio_comp_ctx_2; + AioCompletion *aio_comp_1 = AioCompletion::create_and_start( + &aio_comp_ctx_1, ictx, AIO_TYPE_READ); + + + ReadResult rr; + MockImageReadRequest mock_aio_image_read_1(mock_image_ctx, aio_comp_1, + {{0, 1}}, std::move(rr), 0, {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_read_1.send(); + } + ASSERT_EQ(1, aio_comp_ctx_1.wait()); + + AioCompletion *aio_comp_2 = AioCompletion::create_and_start( + &aio_comp_ctx_2, ictx, AIO_TYPE_READ); + expect_object_request_send(mock_image_ctx, 0); + + MockImageReadRequest mock_aio_image_read_2(mock_image_ctx, aio_comp_2, + {{0, 1}}, std::move(rr), 0, {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_read_2.send(); + } + ASSERT_EQ(1, aio_comp_ctx_2.wait()); +} + +TEST_F(TestMockIoImageRequest, PartialDiscard) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->discard_granularity_bytes = 0; + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.journal = nullptr; + + InSequence seq; + expect_get_modify_timestamp(mock_image_ctx, false); + expect_object_discard_request(mock_image_ctx, 0, 16, 63, 0); + expect_object_discard_request(mock_image_ctx, 0, 84, 100, 0); + + C_SaferCond aio_comp_ctx; + AioCompletion *aio_comp = AioCompletion::create_and_start( + &aio_comp_ctx, ictx, AIO_TYPE_DISCARD); + MockImageDiscardRequest mock_aio_image_discard( + mock_image_ctx, aio_comp, {{16, 63}, {84, 100}}, + ictx->discard_granularity_bytes, {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_discard.send(); + } + ASSERT_EQ(0, aio_comp_ctx.wait()); +} + +TEST_F(TestMockIoImageRequest, TailDiscard) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, resize(ictx, ictx->layout.object_size)); + ictx->discard_granularity_bytes = 2 * ictx->layout.object_size; + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.journal = nullptr; + + InSequence seq; + expect_get_modify_timestamp(mock_image_ctx, false); + expect_object_discard_request( + mock_image_ctx, 0, ictx->layout.object_size - 1024, 1024, 0); + + C_SaferCond aio_comp_ctx; + AioCompletion *aio_comp = AioCompletion::create_and_start( + &aio_comp_ctx, ictx, AIO_TYPE_DISCARD); + MockImageDiscardRequest mock_aio_image_discard( + mock_image_ctx, aio_comp, + {{ictx->layout.object_size - 1024, 1024}}, + ictx->discard_granularity_bytes, {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_discard.send(); + } + ASSERT_EQ(0, aio_comp_ctx.wait()); +} + +TEST_F(TestMockIoImageRequest, DiscardGranularity) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, resize(ictx, ictx->layout.object_size)); + ictx->discard_granularity_bytes = 32; + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.journal = nullptr; + + InSequence seq; + expect_get_modify_timestamp(mock_image_ctx, false); + expect_object_discard_request(mock_image_ctx, 0, 32, 32, 0); + expect_object_discard_request(mock_image_ctx, 0, 96, 64, 0); + expect_object_discard_request( + mock_image_ctx, 0, ictx->layout.object_size - 32, 32, 0); + + C_SaferCond aio_comp_ctx; + AioCompletion *aio_comp = AioCompletion::create_and_start( + &aio_comp_ctx, ictx, AIO_TYPE_DISCARD); + MockImageDiscardRequest mock_aio_image_discard( + mock_image_ctx, aio_comp, + {{16, 63}, {96, 31}, {84, 100}, {ictx->layout.object_size - 33, 33}}, + ictx->discard_granularity_bytes, {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_discard.send(); + } + ASSERT_EQ(0, aio_comp_ctx.wait()); +} + +TEST_F(TestMockIoImageRequest, AioWriteJournalAppendDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockTestJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + InSequence seq; + expect_get_modify_timestamp(mock_image_ctx, false); + expect_is_journal_appending(mock_journal, false); + expect_object_request_send(mock_image_ctx, 0); + + C_SaferCond aio_comp_ctx; + AioCompletion *aio_comp = AioCompletion::create_and_start( + &aio_comp_ctx, ictx, AIO_TYPE_WRITE); + + bufferlist bl; + bl.append("1"); + MockImageWriteRequest mock_aio_image_write(mock_image_ctx, aio_comp, + {{0, 1}}, std::move(bl), 0, {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_write.send(); + } + ASSERT_EQ(0, aio_comp_ctx.wait()); +} + +TEST_F(TestMockIoImageRequest, AioDiscardJournalAppendDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->discard_granularity_bytes = 0; + + MockTestImageCtx mock_image_ctx(*ictx); + MockTestJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + InSequence seq; + expect_get_modify_timestamp(mock_image_ctx, false); + expect_is_journal_appending(mock_journal, false); + expect_object_request_send(mock_image_ctx, 0); + + C_SaferCond aio_comp_ctx; + AioCompletion *aio_comp = AioCompletion::create_and_start( + &aio_comp_ctx, ictx, AIO_TYPE_DISCARD); + MockImageDiscardRequest mock_aio_image_discard( + mock_image_ctx, aio_comp, {{0, 1}}, ictx->discard_granularity_bytes, {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_discard.send(); + } + ASSERT_EQ(0, aio_comp_ctx.wait()); +} + +TEST_F(TestMockIoImageRequest, AioFlushJournalAppendDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockTestJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_is_journal_appending(mock_journal, false); + expect_object_request_send(mock_image_ctx, 0); + + C_SaferCond aio_comp_ctx; + AioCompletion *aio_comp = AioCompletion::create_and_start( + &aio_comp_ctx, ictx, AIO_TYPE_FLUSH); + MockImageFlushRequest mock_aio_image_flush(mock_image_ctx, aio_comp, + FLUSH_SOURCE_USER, {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_flush.send(); + } + ASSERT_EQ(0, aio_comp_ctx.wait()); +} + +TEST_F(TestMockIoImageRequest, AioWriteSameJournalAppendDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockTestJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + InSequence seq; + expect_get_modify_timestamp(mock_image_ctx, false); + expect_is_journal_appending(mock_journal, false); + expect_object_request_send(mock_image_ctx, 0); + + C_SaferCond aio_comp_ctx; + AioCompletion *aio_comp = AioCompletion::create_and_start( + &aio_comp_ctx, ictx, AIO_TYPE_WRITESAME); + + bufferlist bl; + bl.append("1"); + MockImageWriteSameRequest mock_aio_image_writesame(mock_image_ctx, aio_comp, + {{0, 1}}, std::move(bl), 0, + {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_writesame.send(); + } + ASSERT_EQ(0, aio_comp_ctx.wait()); +} + +TEST_F(TestMockIoImageRequest, AioCompareAndWriteJournalAppendDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockTestJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + InSequence seq; + expect_get_modify_timestamp(mock_image_ctx, false); + expect_is_journal_appending(mock_journal, false); + expect_object_request_send(mock_image_ctx, 0); + + C_SaferCond aio_comp_ctx; + AioCompletion *aio_comp = AioCompletion::create_and_start( + &aio_comp_ctx, ictx, AIO_TYPE_COMPARE_AND_WRITE); + + bufferlist cmp_bl; + cmp_bl.append("1"); + bufferlist write_bl; + write_bl.append("1"); + uint64_t mismatch_offset; + MockImageCompareAndWriteRequest mock_aio_image_write(mock_image_ctx, aio_comp, + {{0, 1}}, std::move(cmp_bl), + std::move(write_bl), + &mismatch_offset, + 0, {}); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_aio_image_write.send(); + } + ASSERT_EQ(0, aio_comp_ctx.wait()); +} + +} // namespace io +} // namespace librbd diff --git a/src/test/librbd/io/test_mock_ImageRequestWQ.cc b/src/test/librbd/io/test_mock_ImageRequestWQ.cc new file mode 100644 index 00000000..7b91d68f --- /dev/null +++ b/src/test/librbd/io/test_mock_ImageRequestWQ.cc @@ -0,0 +1,465 @@ +// -*- 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/exclusive_lock/MockPolicy.h" +#include "librbd/io/ImageDispatchSpec.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/io/ImageRequest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace io { + +template <> +struct ImageRequest<librbd::MockTestImageCtx> { + static ImageRequest* s_instance; + AioCompletion *aio_comp = nullptr; + + static void aio_write(librbd::MockTestImageCtx *ictx, AioCompletion *c, + Extents &&image_extents, bufferlist &&bl, int op_flags, + const ZTracer::Trace &parent_trace) { + } + + ImageRequest() { + s_instance = this; + } +}; + +template <> +struct ImageDispatchSpec<librbd::MockTestImageCtx> { + static ImageDispatchSpec* s_instance; + AioCompletion *aio_comp = nullptr; + + static ImageDispatchSpec* create_write_request( + librbd::MockTestImageCtx &image_ctx, AioCompletion *aio_comp, + Extents &&image_extents, bufferlist &&bl, int op_flags, + const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_comp = aio_comp; + return s_instance; + } + + static ImageDispatchSpec* create_flush_request( + librbd::MockTestImageCtx &image_ctx, AioCompletion *aio_comp, + FlushSource flush_source, const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_comp = aio_comp; + return s_instance; + } + + MOCK_CONST_METHOD0(is_write_op, bool()); + MOCK_CONST_METHOD0(start_op, void()); + MOCK_CONST_METHOD0(send, void()); + MOCK_CONST_METHOD1(fail, void(int)); + MOCK_CONST_METHOD1(was_throttled, bool(uint64_t)); + MOCK_CONST_METHOD0(were_all_throttled, bool()); + MOCK_CONST_METHOD1(set_throttled, void(uint64_t)); + MOCK_CONST_METHOD2(tokens_requested, bool(uint64_t, uint64_t *)); + + ImageDispatchSpec() { + s_instance = this; + } +}; +} // namespace io + +namespace util { + +inline ImageCtx *get_image_ctx(MockTestImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util + +} // namespace librbd + +template <> +struct ThreadPool::PointerWQ<librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx>> { + typedef librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx> ImageDispatchSpec; + static PointerWQ* s_instance; + + ceph::mutex m_lock; + + PointerWQ(const std::string &name, time_t, int, ThreadPool *) + : m_lock(ceph::make_mutex(name)) { + s_instance = this; + } + virtual ~PointerWQ() { + } + + MOCK_METHOD0(drain, void()); + MOCK_METHOD0(empty, bool()); + MOCK_METHOD0(mock_empty, bool()); + MOCK_METHOD0(signal, void()); + MOCK_METHOD1(signal, void(const std::lock_guard<ceph::mutex>&)); + MOCK_METHOD0(process_finish, void()); + + MOCK_METHOD0(front, ImageDispatchSpec*()); + MOCK_METHOD1(requeue_front, void(ImageDispatchSpec*)); + MOCK_METHOD2(requeue_back, void(const std::lock_guard<ceph::mutex>&, + ImageDispatchSpec*)); + + MOCK_METHOD0(dequeue, void*()); + MOCK_METHOD1(queue, void(ImageDispatchSpec*)); + + void register_work_queue() { + // no-op + } + ceph::mutex &get_pool_lock() { + return m_lock; + } + + void* invoke_dequeue() { + std::lock_guard locker{m_lock}; + return _void_dequeue(); + } + void invoke_process(ImageDispatchSpec *image_request) { + process(image_request); + } + + virtual void *_void_dequeue() { + return dequeue(); + } + virtual void process(ImageDispatchSpec *req) = 0; + virtual bool _empty() { + return mock_empty(); + } + +}; + +ThreadPool::PointerWQ<librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx>>* + ThreadPool::PointerWQ<librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx>>::s_instance = nullptr; +librbd::io::ImageRequest<librbd::MockTestImageCtx>* + librbd::io::ImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr; +librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx>* + librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx>::s_instance = nullptr; + +#include "librbd/io/ImageRequestWQ.cc" + +namespace librbd { +namespace io { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; + +struct TestMockIoImageRequestWQ : public TestMockFixture { + typedef ImageRequestWQ<librbd::MockTestImageCtx> MockImageRequestWQ; + typedef ImageRequest<librbd::MockTestImageCtx> MockImageRequest; + typedef ImageDispatchSpec<librbd::MockTestImageCtx> MockImageDispatchSpec; + + void expect_is_write_op(MockImageDispatchSpec &image_request, + bool write_op) { + EXPECT_CALL(image_request, is_write_op()).WillOnce(Return(write_op)); + } + + void expect_signal(MockImageRequestWQ &image_request_wq) { + EXPECT_CALL(image_request_wq, signal()); + } + + void expect_signal_locked(MockImageRequestWQ &image_request_wq) { + EXPECT_CALL(image_request_wq, signal(_)); + } + + void expect_queue(MockImageRequestWQ &image_request_wq) { + EXPECT_CALL(image_request_wq, queue(_)); + } + + void expect_requeue_back(MockImageRequestWQ &image_request_wq) { + EXPECT_CALL(image_request_wq, requeue_back(_, _)); + } + + void expect_front(MockImageRequestWQ &image_request_wq, + MockImageDispatchSpec *image_request) { + EXPECT_CALL(image_request_wq, front()).WillOnce(Return(image_request)); + } + + void expect_is_refresh_request(MockTestImageCtx &mock_image_ctx, + bool required) { + EXPECT_CALL(*mock_image_ctx.state, is_refresh_required()).WillOnce( + Return(required)); + } + + void expect_dequeue(MockImageRequestWQ &image_request_wq, + MockImageDispatchSpec *image_request) { + EXPECT_CALL(image_request_wq, dequeue()).WillOnce(Return(image_request)); + } + + void expect_get_exclusive_lock_policy(MockTestImageCtx &mock_image_ctx, + librbd::exclusive_lock::MockPolicy &policy) { + EXPECT_CALL(mock_image_ctx, + get_exclusive_lock_policy()).WillOnce(Return(&policy)); + } + + void expect_may_auto_request_lock(librbd::exclusive_lock::MockPolicy &policy, + bool value) { + EXPECT_CALL(policy, may_auto_request_lock()).WillOnce(Return(value)); + } + + void expect_acquire_lock(MockExclusiveLock &mock_exclusive_lock, + Context **on_finish) { + EXPECT_CALL(mock_exclusive_lock, acquire_lock(_)) + .WillOnce(Invoke([on_finish](Context *ctx) { + *on_finish = ctx; + })); + } + + void expect_process_finish(MockImageRequestWQ &mock_image_request_wq) { + EXPECT_CALL(mock_image_request_wq, process_finish()).Times(1); + } + + void expect_fail(MockImageDispatchSpec &mock_image_request, int r) { + EXPECT_CALL(mock_image_request, fail(r)) + .WillOnce(Invoke([&mock_image_request](int r) { + mock_image_request.aio_comp->get(); + mock_image_request.aio_comp->fail(r); + })); + } + + void expect_refresh(MockTestImageCtx &mock_image_ctx, Context **on_finish) { + EXPECT_CALL(*mock_image_ctx.state, refresh(_)) + .WillOnce(Invoke([on_finish](Context *ctx) { + *on_finish = ctx; + })); + } + + void expect_set_throttled(MockImageDispatchSpec &mock_image_request) { + EXPECT_CALL(mock_image_request, set_throttled(_)).Times(6); + } + + void expect_was_throttled(MockImageDispatchSpec &mock_image_request, bool value) { + EXPECT_CALL(mock_image_request, was_throttled(_)).Times(6).WillRepeatedly(Return(value)); + } + + void expect_tokens_requested(MockImageDispatchSpec &mock_image_request, + uint64_t tokens, bool r) { + EXPECT_CALL(mock_image_request, tokens_requested(_, _)) + .WillOnce(WithArg<1>(Invoke([tokens, r](uint64_t *t) { + *t = tokens; + return r; + }))); + } + + void expect_all_throttled(MockImageDispatchSpec &mock_image_request, bool value) { + EXPECT_CALL(mock_image_request, were_all_throttled()).WillOnce(Return(value)); + } + + void expect_start_op(MockImageDispatchSpec &mock_image_request) { + EXPECT_CALL(mock_image_request, start_op()).Times(1); + } +}; + +TEST_F(TestMockIoImageRequestWQ, AcquireLockError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + auto mock_queued_image_request = new MockImageDispatchSpec(); + expect_was_throttled(*mock_queued_image_request, false); + expect_set_throttled(*mock_queued_image_request); + + InSequence seq; + MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr); + expect_signal(mock_image_request_wq); + mock_image_request_wq.set_require_lock(DIRECTION_WRITE, true); + + expect_is_write_op(*mock_queued_image_request, true); + expect_queue(mock_image_request_wq); + auto *aio_comp = new librbd::io::AioCompletion(); + mock_image_request_wq.aio_write(aio_comp, 0, 0, {}, 0); + + librbd::exclusive_lock::MockPolicy mock_exclusive_lock_policy; + expect_front(mock_image_request_wq, mock_queued_image_request); + expect_is_refresh_request(mock_image_ctx, false); + expect_is_write_op(*mock_queued_image_request, true); + expect_dequeue(mock_image_request_wq, mock_queued_image_request); + expect_get_exclusive_lock_policy(mock_image_ctx, mock_exclusive_lock_policy); + expect_may_auto_request_lock(mock_exclusive_lock_policy, true); + Context *on_acquire = nullptr; + expect_acquire_lock(mock_exclusive_lock, &on_acquire); + ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == nullptr); + ASSERT_TRUE(on_acquire != nullptr); + + expect_process_finish(mock_image_request_wq); + expect_fail(*mock_queued_image_request, -EPERM); + expect_is_write_op(*mock_queued_image_request, true); + expect_signal(mock_image_request_wq); + on_acquire->complete(-EPERM); + + ASSERT_EQ(0, aio_comp->wait_for_complete()); + ASSERT_EQ(-EPERM, aio_comp->get_return_value()); + aio_comp->release(); +} + +TEST_F(TestMockIoImageRequestWQ, AcquireLockBlacklisted) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + auto mock_queued_image_request = new MockImageDispatchSpec(); + expect_was_throttled(*mock_queued_image_request, false); + expect_set_throttled(*mock_queued_image_request); + + InSequence seq; + MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr); + expect_signal(mock_image_request_wq); + mock_image_request_wq.set_require_lock(DIRECTION_WRITE, true); + + expect_is_write_op(*mock_queued_image_request, true); + expect_queue(mock_image_request_wq); + auto *aio_comp = new librbd::io::AioCompletion(); + mock_image_request_wq.aio_write(aio_comp, 0, 0, {}, 0); + + librbd::exclusive_lock::MockPolicy mock_exclusive_lock_policy; + expect_front(mock_image_request_wq, mock_queued_image_request); + expect_is_refresh_request(mock_image_ctx, false); + expect_is_write_op(*mock_queued_image_request, true); + expect_dequeue(mock_image_request_wq, mock_queued_image_request); + expect_get_exclusive_lock_policy(mock_image_ctx, mock_exclusive_lock_policy); + expect_may_auto_request_lock(mock_exclusive_lock_policy, false); + EXPECT_CALL(*mock_image_ctx.exclusive_lock, get_unlocked_op_error()) + .WillOnce(Return(-EBLACKLISTED)); + expect_process_finish(mock_image_request_wq); + expect_fail(*mock_queued_image_request, -EBLACKLISTED); + expect_is_write_op(*mock_queued_image_request, true); + expect_signal(mock_image_request_wq); + ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == nullptr); + + ASSERT_EQ(0, aio_comp->wait_for_complete()); + ASSERT_EQ(-EBLACKLISTED, aio_comp->get_return_value()); + aio_comp->release(); +} + +TEST_F(TestMockIoImageRequestWQ, RefreshError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + auto mock_queued_image_request = new MockImageDispatchSpec(); + expect_was_throttled(*mock_queued_image_request, false); + expect_set_throttled(*mock_queued_image_request); + + InSequence seq; + MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr); + + expect_is_write_op(*mock_queued_image_request, true); + expect_queue(mock_image_request_wq); + auto *aio_comp = new librbd::io::AioCompletion(); + mock_image_request_wq.aio_write(aio_comp, 0, 0, {}, 0); + + expect_front(mock_image_request_wq, mock_queued_image_request); + expect_is_refresh_request(mock_image_ctx, true); + expect_is_write_op(*mock_queued_image_request, true); + expect_dequeue(mock_image_request_wq, mock_queued_image_request); + Context *on_refresh = nullptr; + expect_refresh(mock_image_ctx, &on_refresh); + ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == nullptr); + ASSERT_TRUE(on_refresh != nullptr); + + expect_process_finish(mock_image_request_wq); + expect_fail(*mock_queued_image_request, -EPERM); + expect_is_write_op(*mock_queued_image_request, true); + expect_signal(mock_image_request_wq); + on_refresh->complete(-EPERM); + + ASSERT_EQ(0, aio_comp->wait_for_complete()); + ASSERT_EQ(-EPERM, aio_comp->get_return_value()); + aio_comp->release(); +} + +TEST_F(TestMockIoImageRequestWQ, QosNoLimit) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + MockImageDispatchSpec mock_queued_image_request; + expect_was_throttled(mock_queued_image_request, false); + expect_set_throttled(mock_queued_image_request); + + InSequence seq; + MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr); + + mock_image_request_wq.apply_qos_limit(RBD_QOS_BPS_THROTTLE, 0, 0); + + expect_front(mock_image_request_wq, &mock_queued_image_request); + expect_is_refresh_request(mock_image_ctx, false); + expect_is_write_op(mock_queued_image_request, true); + expect_dequeue(mock_image_request_wq, &mock_queued_image_request); + expect_start_op(mock_queued_image_request); + ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == &mock_queued_image_request); +} + +TEST_F(TestMockIoImageRequestWQ, BPSQosNoBurst) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + MockImageDispatchSpec mock_queued_image_request; + expect_was_throttled(mock_queued_image_request, false); + expect_set_throttled(mock_queued_image_request); + + InSequence seq; + MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr); + + mock_image_request_wq.apply_qos_limit(RBD_QOS_BPS_THROTTLE, 1, 0); + + expect_front(mock_image_request_wq, &mock_queued_image_request); + expect_tokens_requested(mock_queued_image_request, 2, true); + expect_dequeue(mock_image_request_wq, &mock_queued_image_request); + expect_all_throttled(mock_queued_image_request, true); + expect_requeue_back(mock_image_request_wq); + expect_signal_locked(mock_image_request_wq); + ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == nullptr); +} + +TEST_F(TestMockIoImageRequestWQ, BPSQosWithBurst) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + MockImageDispatchSpec mock_queued_image_request; + expect_was_throttled(mock_queued_image_request, false); + expect_set_throttled(mock_queued_image_request); + + InSequence seq; + MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr); + + mock_image_request_wq.apply_qos_limit(RBD_QOS_BPS_THROTTLE, 1, 1); + + expect_front(mock_image_request_wq, &mock_queued_image_request); + expect_tokens_requested(mock_queued_image_request, 2, true); + expect_dequeue(mock_image_request_wq, &mock_queued_image_request); + expect_all_throttled(mock_queued_image_request, true); + expect_requeue_back(mock_image_request_wq); + expect_signal_locked(mock_image_request_wq); + ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == nullptr); +} + +} // namespace io +} // namespace librbd diff --git a/src/test/librbd/io/test_mock_ObjectRequest.cc b/src/test/librbd/io/test_mock_ObjectRequest.cc new file mode 100644 index 00000000..2f1d8777 --- /dev/null +++ b/src/test/librbd/io/test_mock_ObjectRequest.cc @@ -0,0 +1,1394 @@ +// -*- 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/MockObjectMap.h" +#include "test/librbd/mock/cache/MockImageCache.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "include/rbd/librbd.hpp" +#include "librbd/io/CopyupRequest.h" +#include "librbd/io/ImageRequest.h" +#include "librbd/io/ObjectRequest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace util { + +inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util + +namespace io { + +template <> +struct CopyupRequest<librbd::MockImageCtx> { + MOCK_METHOD0(send, void()); + MOCK_METHOD1(append_request, void(AbstractObjectWriteRequest<librbd::MockTestImageCtx>*)); +}; + +template <> +struct CopyupRequest<librbd::MockTestImageCtx> : public CopyupRequest<librbd::MockImageCtx> { + static CopyupRequest* s_instance; + static CopyupRequest* create(librbd::MockTestImageCtx *ictx, + const std::string &oid, uint64_t objectno, + Extents &&image_extents, + const ZTracer::Trace &parent_trace) { + return s_instance; + } + + CopyupRequest() { + s_instance = this; + } +}; + +template <> +struct ImageRequest<librbd::MockTestImageCtx> { + static ImageRequest *s_instance; + static void aio_read(librbd::MockImageCtx *ictx, AioCompletion *c, + Extents &&image_extents, ReadResult &&read_result, + int op_flags, const ZTracer::Trace &parent_trace) { + s_instance->aio_read(c, image_extents); + } + + MOCK_METHOD2(aio_read, void(AioCompletion *, const Extents&)); + + ImageRequest() { + s_instance = this; + } +}; + +CopyupRequest<librbd::MockTestImageCtx>* CopyupRequest<librbd::MockTestImageCtx>::s_instance = nullptr; +ImageRequest<librbd::MockTestImageCtx>* ImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace io +} // namespace librbd + +#include "librbd/io/ObjectRequest.cc" + +namespace librbd { +namespace io { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::WithArgs; + +struct TestMockIoObjectRequest : public TestMockFixture { + typedef ObjectRequest<librbd::MockTestImageCtx> MockObjectRequest; + typedef ObjectReadRequest<librbd::MockTestImageCtx> MockObjectReadRequest; + typedef ObjectWriteRequest<librbd::MockTestImageCtx> MockObjectWriteRequest; + typedef ObjectDiscardRequest<librbd::MockTestImageCtx> MockObjectDiscardRequest; + typedef ObjectWriteSameRequest<librbd::MockTestImageCtx> MockObjectWriteSameRequest; + typedef ObjectCompareAndWriteRequest<librbd::MockTestImageCtx> MockObjectCompareAndWriteRequest; + typedef AbstractObjectWriteRequest<librbd::MockTestImageCtx> MockAbstractObjectWriteRequest; + typedef CopyupRequest<librbd::MockTestImageCtx> MockCopyupRequest; + typedef ImageRequest<librbd::MockTestImageCtx> MockImageRequest; + + void expect_object_may_exist(MockTestImageCtx &mock_image_ctx, + uint64_t object_no, bool exists) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, object_may_exist(object_no)) + .WillOnce(Return(exists)); + } + } + + void expect_get_object_size(MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, get_object_size()).WillRepeatedly(Return( + mock_image_ctx.layout.object_size)); + } + + void expect_get_parent_overlap(MockTestImageCtx &mock_image_ctx, + librados::snap_t snap_id, uint64_t overlap, + int r) { + EXPECT_CALL(mock_image_ctx, get_parent_overlap(snap_id, _)) + .WillOnce(WithArg<1>(Invoke([overlap, r](uint64_t *o) { + *o = overlap; + return r; + }))); + } + + void expect_prune_parent_extents(MockTestImageCtx &mock_image_ctx, + const Extents& extents, + uint64_t overlap, uint64_t object_overlap) { + EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, overlap)) + .WillOnce(WithArg<0>(Invoke([extents, object_overlap](Extents& e) { + e = extents; + return object_overlap; + }))); + } + + void expect_get_read_flags(MockTestImageCtx &mock_image_ctx, + librados::snap_t snap_id, int flags) { + EXPECT_CALL(mock_image_ctx, get_read_flags(snap_id)) + .WillOnce(Return(flags)); + } + + void expect_is_lock_owner(MockExclusiveLock& mock_exclusive_lock) { + EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillRepeatedly( + Return(true)); + } + + void expect_read(MockTestImageCtx &mock_image_ctx, + const std::string& oid, uint64_t off, uint64_t len, + const std::string& data, int r) { + bufferlist bl; + bl.append(data); + + auto& expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + read(oid, len, off, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(WithArg<3>(Invoke([bl](bufferlist *out_bl) { + out_bl->append(bl); + return bl.length(); + }))); + } + } + + void expect_sparse_read(MockTestImageCtx &mock_image_ctx, + const std::string& oid, uint64_t off, uint64_t len, + const std::string& data, int r) { + bufferlist bl; + bl.append(data); + + auto& expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + sparse_read(oid, off, len, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(WithArg<4>(Invoke([bl](bufferlist *out_bl) { + out_bl->append(bl); + return bl.length(); + }))); + } + } + + void expect_aio_read(MockTestImageCtx &mock_image_ctx, + MockImageRequest& mock_image_request, + Extents&& extents, int r) { + EXPECT_CALL(mock_image_request, aio_read(_, extents)) + .WillOnce(WithArg<0>(Invoke([&mock_image_ctx, r](AioCompletion* aio_comp) { + aio_comp->set_request_count(1); + mock_image_ctx.image_ctx->op_work_queue->queue(new C_AioRequest(aio_comp), r); + }))); + } + + void expect_copyup(MockCopyupRequest& mock_copyup_request, int r) { + EXPECT_CALL(mock_copyup_request, send()) + .WillOnce(Invoke([]() {})); + } + + void expect_copyup(MockCopyupRequest& mock_copyup_request, + MockAbstractObjectWriteRequest** write_request, int r) { + EXPECT_CALL(mock_copyup_request, append_request(_)) + .WillOnce(Invoke([write_request](MockAbstractObjectWriteRequest *req) { + *write_request = req; + })); + EXPECT_CALL(mock_copyup_request, send()) + .WillOnce(Invoke([write_request, r]() { + (*write_request)->handle_copyup(r); + })); + } + + void expect_object_map_update(MockTestImageCtx &mock_image_ctx, + uint64_t start_object, uint64_t end_object, + uint8_t state, + const boost::optional<uint8_t> ¤t_state, + bool updated, int ret_val) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, + aio_update(CEPH_NOSNAP, start_object, end_object, state, + current_state, _, false, _)) + .WillOnce(WithArg<7>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) { + if (updated) { + mock_image_ctx.op_work_queue->queue(ctx, ret_val); + } + return updated; + }))); + } + } + + void expect_assert_exists(MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), assert_exists(_)) + .WillOnce(Return(r)); + } + + void expect_write(MockTestImageCtx &mock_image_ctx, + uint64_t offset, uint64_t length, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + write(_, _, length, offset, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_write_full(MockTestImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + write_full(_, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_writesame(MockTestImageCtx &mock_image_ctx, + uint64_t offset, uint64_t length, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + writesame(_, _, length, offset, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_remove(MockTestImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + remove(_, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_create(MockTestImageCtx &mock_image_ctx, bool exclusive) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), create(_, exclusive)) + .Times(1); + } + + void expect_truncate(MockTestImageCtx &mock_image_ctx, int offset, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + truncate(_, offset, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_zero(MockTestImageCtx &mock_image_ctx, int offset, int length, + int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + zero(_, offset, length, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_cmpext(MockTestImageCtx &mock_image_ctx, int offset, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + cmpext(_, offset, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } +}; + +TEST_F(TestMockIoObjectRequest, Read) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->sparse_read_threshold_bytes = 8096; + + MockTestImageCtx mock_image_ctx(*ictx); + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); + expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, + std::string(4096, '1'), 0); + + bufferlist bl; + ExtentMap extent_map; + C_SaferCond ctx; + auto req = MockObjectReadRequest::create( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, {}, + &bl, &extent_map, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, SparseReadThreshold) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->sparse_read_threshold_bytes = ictx->get_object_size(); + + MockTestImageCtx mock_image_ctx(*ictx); + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); + expect_sparse_read(mock_image_ctx, ictx->get_object_name(0), 0, + ictx->sparse_read_threshold_bytes, + std::string(ictx->sparse_read_threshold_bytes, '1'), 0); + + bufferlist bl; + ExtentMap extent_map; + C_SaferCond ctx; + auto req = MockObjectReadRequest::create( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, + ictx->sparse_read_threshold_bytes, CEPH_NOSNAP, 0, {}, &bl, &extent_map, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, ReadError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->sparse_read_threshold_bytes = 8096; + + MockTestImageCtx mock_image_ctx(*ictx); + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); + expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -EPERM); + + bufferlist bl; + ExtentMap extent_map; + C_SaferCond ctx; + auto req = MockObjectReadRequest::create( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, {}, + &bl, &extent_map, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, ParentRead) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + ictx->sparse_read_threshold_bytes = 8096; + ictx->clone_copy_on_read = false; + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.parent = &mock_image_ctx; + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); + expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT); + + MockImageRequest mock_image_request; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_aio_read(mock_image_ctx, mock_image_request, {{0, 4096}}, 0); + + bufferlist bl; + ExtentMap extent_map; + C_SaferCond ctx; + auto req = MockObjectReadRequest::create( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, {}, + &bl, &extent_map, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, ParentReadError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + ictx->sparse_read_threshold_bytes = 8096; + ictx->clone_copy_on_read = false; + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.parent = &mock_image_ctx; + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); + expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT); + + MockImageRequest mock_image_request; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_aio_read(mock_image_ctx, mock_image_request, {{0, 4096}}, -EPERM); + + bufferlist bl; + ExtentMap extent_map; + C_SaferCond ctx; + auto req = MockObjectReadRequest::create( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, {}, + &bl, &extent_map, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CopyOnRead) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + ictx->sparse_read_threshold_bytes = 8096; + ictx->clone_copy_on_read = true; + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.parent = &mock_image_ctx; + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); + expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT); + + MockImageRequest mock_image_request; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_aio_read(mock_image_ctx, mock_image_request, {{0, 4096}}, 0); + + MockCopyupRequest mock_copyup_request; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_copyup(mock_copyup_request, 0); + + bufferlist bl; + ExtentMap extent_map; + C_SaferCond ctx; + auto req = MockObjectReadRequest::create( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, {}, + &bl, &extent_map, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, Write) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_write(mock_image_ctx, 0, 4096, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl), + mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, WriteFull) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist bl; + bl.append(std::string(ictx->get_object_size(), '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_write_full(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl), + mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, WriteObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, true, 0); + expect_write(mock_image_ctx, 0, 4096, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl), + mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, WriteError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_write(mock_image_ctx, 0, 4096, -EPERM); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl), + mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, Copyup) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_assert_exists(mock_image_ctx, -ENOENT); + + MockAbstractObjectWriteRequest *write_request = nullptr; + MockCopyupRequest mock_copyup_request; + expect_copyup(mock_copyup_request, &write_request, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl), + mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CopyupRestart) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_assert_exists(mock_image_ctx, -ENOENT); + + MockAbstractObjectWriteRequest *write_request = nullptr; + MockCopyupRequest mock_copyup_request; + expect_copyup(mock_copyup_request, &write_request, -ERESTART); + expect_assert_exists(mock_image_ctx, 0); + expect_write(mock_image_ctx, 0, 4096, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl), + mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CopyupOptimization) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_OBJECT_MAP); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_object_may_exist(mock_image_ctx, 0, false); + + MockAbstractObjectWriteRequest *write_request = nullptr; + MockCopyupRequest mock_copyup_request; + expect_copyup(mock_copyup_request, &write_request, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl), + mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CopyupError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_assert_exists(mock_image_ctx, -ENOENT); + + MockAbstractObjectWriteRequest *write_request = nullptr; + MockCopyupRequest mock_copyup_request; + expect_copyup(mock_copyup_request, &write_request, -EPERM); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl), + mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardRemove) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_PENDING, {}, false, 0); + expect_remove(mock_image_ctx, 0); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_NONEXISTENT, + OBJECT_PENDING, false, 0); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, + mock_image_ctx.get_object_size(), mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardRemoveTruncate) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + uint64_t features; + ASSERT_EQ(0, image.features(&features)); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), features, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_object_may_exist(mock_image_ctx, 0, false); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_create(mock_image_ctx, false); + expect_truncate(mock_image_ctx, 0, 0); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, + mock_image_ctx.get_object_size(), mock_image_ctx.snapc, + OBJECT_DISCARD_FLAG_DISABLE_CLONE_REMOVE, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardTruncateAssertExists) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + uint64_t features; + ASSERT_EQ(0, image.features(&features)); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), features, &order)); + ASSERT_EQ(0, rbd.open(m_ioctx, image, clone_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + image.close(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_assert_exists(mock_image_ctx, 0); + expect_truncate(mock_image_ctx, 0, 0); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), create(_, _)).Times(0); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, + mock_image_ctx.get_object_size(), mock_image_ctx.snapc, + OBJECT_DISCARD_FLAG_DISABLE_CLONE_REMOVE, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardTruncate) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_truncate(mock_image_ctx, 1, 0); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), create(_, _)).Times(0); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 1, + mock_image_ctx.get_object_size() - 1, mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardZero) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_zero(mock_image_ctx, 1, 1, 0); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 1, 1, mock_image_ctx.snapc, + 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardDisableObjectMapUpdate) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_remove(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, + mock_image_ctx.get_object_size(), mock_image_ctx.snapc, + OBJECT_DISCARD_FLAG_DISABLE_CLONE_REMOVE | + OBJECT_DISCARD_FLAG_DISABLE_OBJECT_MAP_UPDATE, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardNoOp) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, false); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, + mock_image_ctx.get_object_size(), mock_image_ctx.snapc, + OBJECT_DISCARD_FLAG_DISABLE_CLONE_REMOVE | + OBJECT_DISCARD_FLAG_DISABLE_OBJECT_MAP_UPDATE, {}, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, WriteSame) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_writesame(mock_image_ctx, 0, 4096, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteSameRequest::create_write_same( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, std::move(bl), + mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CompareAndWrite) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist cmp_bl; + cmp_bl.append_zero(4096); + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_cmpext(mock_image_ctx, 0, 0); + expect_write(mock_image_ctx, 0, 4096, 0); + + C_SaferCond ctx; + uint64_t mismatch_offset; + auto req = MockObjectWriteSameRequest::create_compare_and_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(cmp_bl), + std::move(bl), mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CompareAndWriteFull) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist cmp_bl; + cmp_bl.append_zero(ictx->get_object_size()); + + bufferlist bl; + bl.append(std::string(ictx->get_object_size(), '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_cmpext(mock_image_ctx, 0, 0); + expect_write_full(mock_image_ctx, 0); + + C_SaferCond ctx; + uint64_t mismatch_offset; + auto req = MockObjectWriteSameRequest::create_compare_and_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(cmp_bl), + std::move(bl), mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CompareAndWriteCopyup) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist cmp_bl; + cmp_bl.append_zero(4096); + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_assert_exists(mock_image_ctx, -ENOENT); + + MockAbstractObjectWriteRequest *write_request = nullptr; + MockCopyupRequest mock_copyup_request; + expect_copyup(mock_copyup_request, &write_request, 0); + + expect_assert_exists(mock_image_ctx, 0); + expect_cmpext(mock_image_ctx, 0, 0); + expect_write(mock_image_ctx, 0, 4096, 0); + + C_SaferCond ctx; + uint64_t mismatch_offset; + auto req = MockObjectWriteSameRequest::create_compare_and_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(cmp_bl), + std::move(bl), mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CompareAndWriteMismatch) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist cmp_bl; + cmp_bl.append_zero(4096); + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_cmpext(mock_image_ctx, 0, -MAX_ERRNO - 1); + + C_SaferCond ctx; + uint64_t mismatch_offset; + auto req = MockObjectWriteSameRequest::create_compare_and_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(cmp_bl), + std::move(bl), mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx); + req->send(); + ASSERT_EQ(-EILSEQ, ctx.wait()); + ASSERT_EQ(1ULL, mismatch_offset); +} + +TEST_F(TestMockIoObjectRequest, ObjectMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, true, + -EBLACKLISTED); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl), + mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(-EBLACKLISTED, ctx.wait()); +} + +} // namespace io +} // namespace librbd + diff --git a/src/test/librbd/journal/test_Entries.cc b/src/test/librbd/journal/test_Entries.cc new file mode 100644 index 00000000..7b013e76 --- /dev/null +++ b/src/test/librbd/journal/test_Entries.cc @@ -0,0 +1,226 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "librbd/internal.h" +#include "librbd/Journal.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/journal/Types.h" +#include "journal/Journaler.h" +#include "journal/ReplayEntry.h" +#include "journal/ReplayHandler.h" +#include "journal/Settings.h" +#include <list> +#include <boost/variant.hpp> + +void register_test_journal_entries() { +} + +class TestJournalEntries : public TestFixture { +public: + typedef std::list<journal::Journaler *> Journalers; + + struct ReplayHandler : public journal::ReplayHandler { + Mutex lock; + Cond cond; + bool entries_available; + bool complete; + + ReplayHandler() + : lock("ReplayHandler::lock"), entries_available(false), complete(false) { + } + + void get() override { + } + void put() override { + } + + void handle_entries_available() override { + Mutex::Locker locker(lock); + entries_available = true; + cond.Signal(); + } + + void handle_complete(int r) override { + Mutex::Locker locker(lock); + complete = true; + cond.Signal(); + } + }; + + ReplayHandler m_replay_handler; + Journalers m_journalers; + + void TearDown() override { + for (Journalers::iterator it = m_journalers.begin(); + it != m_journalers.end(); ++it) { + journal::Journaler *journaler = *it; + journaler->stop_replay(); + journaler->shut_down(); + delete journaler; + } + + TestFixture::TearDown(); + } + + journal::Journaler *create_journaler(librbd::ImageCtx *ictx) { + journal::Journaler *journaler = new journal::Journaler( + ictx->md_ctx, ictx->id, "dummy client", {}); + + int r = journaler->register_client(bufferlist()); + if (r < 0) { + ADD_FAILURE() << "failed to register journal client"; + delete journaler; + return NULL; + } + + C_SaferCond cond; + journaler->init(&cond); + r = cond.wait(); + if (r < 0) { + ADD_FAILURE() << "failed to initialize journal client"; + delete journaler; + return NULL; + } + + journaler->start_live_replay(&m_replay_handler, 0.1); + m_journalers.push_back(journaler); + return journaler; + } + + bool wait_for_entries_available(librbd::ImageCtx *ictx) { + Mutex::Locker locker(m_replay_handler.lock); + while (!m_replay_handler.entries_available) { + if (m_replay_handler.cond.WaitInterval(m_replay_handler.lock, + utime_t(10, 0)) != 0) { + return false; + } + } + m_replay_handler.entries_available = false; + return true; + } + + bool get_event_entry(const journal::ReplayEntry &replay_entry, + librbd::journal::EventEntry *event_entry) { + try { + bufferlist data_bl = replay_entry.get_data(); + auto it = data_bl.cbegin(); + decode(*event_entry, it); + } catch (const buffer::error &err) { + return false; + } + return true; + } + +}; + +TEST_F(TestJournalEntries, AioWrite) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + journal::Journaler *journaler = create_journaler(ictx); + ASSERT_TRUE(journaler != NULL); + + std::string buffer(512, '1'); + bufferlist write_bl; + write_bl.append(buffer); + + C_SaferCond cond_ctx; + auto c = librbd::io::AioCompletion::create(&cond_ctx); + c->get(); + ictx->io_work_queue->aio_write(c, 123, buffer.size(), std::move(write_bl), 0); + ASSERT_EQ(0, c->wait_for_complete()); + c->put(); + + ASSERT_TRUE(wait_for_entries_available(ictx)); + + journal::ReplayEntry replay_entry; + ASSERT_TRUE(journaler->try_pop_front(&replay_entry)); + + librbd::journal::EventEntry event_entry; + ASSERT_TRUE(get_event_entry(replay_entry, &event_entry)); + + ASSERT_EQ(librbd::journal::EVENT_TYPE_AIO_WRITE, + event_entry.get_event_type()); + + librbd::journal::AioWriteEvent aio_write_event = + boost::get<librbd::journal::AioWriteEvent>(event_entry.event); + ASSERT_EQ(123U, aio_write_event.offset); + ASSERT_EQ(buffer.size(), aio_write_event.length); + + bufferlist buffer_bl; + buffer_bl.append(buffer); + ASSERT_TRUE(aio_write_event.data.contents_equal(buffer_bl)); + + ASSERT_EQ(librbd::journal::AioWriteEvent::get_fixed_size() + + aio_write_event.data.length(), replay_entry.get_data().length()); +} + +TEST_F(TestJournalEntries, AioDiscard) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + CephContext* cct = reinterpret_cast<CephContext*>(_rados.cct()); + REQUIRE(!cct->_conf.get_val<bool>("rbd_skip_partial_discard")); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + journal::Journaler *journaler = create_journaler(ictx); + ASSERT_TRUE(journaler != NULL); + + C_SaferCond cond_ctx; + auto c = librbd::io::AioCompletion::create(&cond_ctx); + c->get(); + ictx->io_work_queue->aio_discard(c, 123, 234, + ictx->discard_granularity_bytes); + ASSERT_EQ(0, c->wait_for_complete()); + c->put(); + + ASSERT_TRUE(wait_for_entries_available(ictx)); + + journal::ReplayEntry replay_entry; + ASSERT_TRUE(journaler->try_pop_front(&replay_entry)); + + librbd::journal::EventEntry event_entry; + ASSERT_TRUE(get_event_entry(replay_entry, &event_entry)); + + ASSERT_EQ(librbd::journal::EVENT_TYPE_AIO_DISCARD, + event_entry.get_event_type()); + + librbd::journal::AioDiscardEvent aio_discard_event = + boost::get<librbd::journal::AioDiscardEvent>(event_entry.event); + ASSERT_EQ(123U, aio_discard_event.offset); + ASSERT_EQ(234U, aio_discard_event.length); +} + +TEST_F(TestJournalEntries, AioFlush) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + journal::Journaler *journaler = create_journaler(ictx); + ASSERT_TRUE(journaler != NULL); + + C_SaferCond cond_ctx; + auto c = librbd::io::AioCompletion::create(&cond_ctx); + c->get(); + ictx->io_work_queue->aio_flush(c); + ASSERT_EQ(0, c->wait_for_complete()); + c->put(); + + ASSERT_TRUE(wait_for_entries_available(ictx)); + + journal::ReplayEntry replay_entry; + ASSERT_TRUE(journaler->try_pop_front(&replay_entry)); + + librbd::journal::EventEntry event_entry; + ASSERT_TRUE(get_event_entry(replay_entry, &event_entry)); + + ASSERT_EQ(librbd::journal::EVENT_TYPE_AIO_FLUSH, + event_entry.get_event_type()); +} diff --git a/src/test/librbd/journal/test_Replay.cc b/src/test/librbd/journal/test_Replay.cc new file mode 100644 index 00000000..88b01c60 --- /dev/null +++ b/src/test/librbd/journal/test_Replay.cc @@ -0,0 +1,872 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "cls/rbd/cls_rbd_types.h" +#include "cls/journal/cls_journal_types.h" +#include "cls/journal/cls_journal_client.h" +#include "journal/Journaler.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/ImageWatcher.h" +#include "librbd/internal.h" +#include "librbd/Journal.h" +#include "librbd/Operations.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/io/ImageDispatchSpec.h" +#include "librbd/io/ImageRequest.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/io/ReadResult.h" +#include "librbd/journal/Types.h" + +void register_test_journal_replay() { +} + +class TestJournalReplay : public TestFixture { +public: + + int when_acquired_lock(librbd::ImageCtx *ictx) { + C_SaferCond lock_ctx; + { + RWLock::WLocker owner_locker(ictx->owner_lock); + ictx->exclusive_lock->acquire_lock(&lock_ctx); + } + int r = lock_ctx.wait(); + if (r < 0) { + return r; + } + + C_SaferCond refresh_ctx; + ictx->state->refresh(&refresh_ctx); + return refresh_ctx.wait(); + } + + template<typename T> + void inject_into_journal(librbd::ImageCtx *ictx, T event) { + C_SaferCond ctx; + librbd::journal::EventEntry event_entry(event); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + uint64_t tid = ictx->journal->append_io_event(std::move(event_entry),0, 0, + true, 0); + ictx->journal->wait_event(tid, &ctx); + } + ASSERT_EQ(0, ctx.wait()); + } + + void get_journal_commit_position(librbd::ImageCtx *ictx, int64_t *tag, + int64_t *entry) + { + const std::string client_id = ""; + std::string journal_id = ictx->id; + + C_SaferCond close_cond; + ictx->journal->close(&close_cond); + ASSERT_EQ(0, close_cond.wait()); + delete ictx->journal; + ictx->journal = nullptr; + + C_SaferCond cond; + uint64_t minimum_set; + uint64_t active_set; + std::set<cls::journal::Client> registered_clients; + std::string oid = ::journal::Journaler::header_oid(journal_id); + cls::journal::client::get_mutable_metadata(ictx->md_ctx, oid, &minimum_set, + &active_set, ®istered_clients, &cond); + ASSERT_EQ(0, cond.wait()); + std::set<cls::journal::Client>::const_iterator c; + for (c = registered_clients.begin(); c != registered_clients.end(); ++c) { + if (c->id == client_id) { + break; + } + } + if (c == registered_clients.end() || + c->commit_position.object_positions.empty()) { + *tag = 0; + *entry = -1; + } else { + const cls::journal::ObjectPosition &object_position = + *c->commit_position.object_positions.begin(); + *tag = object_position.tag_tid; + *entry = object_position.entry_tid; + } + + C_SaferCond open_cond; + ictx->journal = new librbd::Journal<>(*ictx); + ictx->journal->open(&open_cond); + ASSERT_EQ(0, open_cond.wait()); + } +}; + +TEST_F(TestJournalReplay, AioDiscardEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + // write to the image w/o using the journal + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->features &= ~RBD_FEATURE_JOURNALING; + + std::string payload(4096, '1'); + bufferlist payload_bl; + payload_bl.append(payload); + auto aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_write(aio_comp, 0, payload.size(), + std::move(payload_bl), 0); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_flush(aio_comp); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + std::string read_payload(4096, '\0'); + librbd::io::ReadResult read_result{&read_payload[0], read_payload.size()}; + aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_read(aio_comp, 0, read_payload.size(), + librbd::io::ReadResult{read_result}, 0); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + ASSERT_EQ(payload, read_payload); + close_image(ictx); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject a discard operation into the journal + inject_into_journal(ictx, + librbd::journal::AioDiscardEvent( + 0, payload.size(), ictx->discard_granularity_bytes)); + close_image(ictx); + + // re-open the journal so that it replays the new entry + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_read(aio_comp, 0, read_payload.size(), + librbd::io::ReadResult{read_result}, 0); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + if (ictx->discard_granularity_bytes > 0) { + ASSERT_EQ(payload, read_payload); + } else { + ASSERT_EQ(std::string(read_payload.size(), '\0'), read_payload); + } + + // check the commit position is properly updated + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + // replay several envents and check the commit position + inject_into_journal(ictx, + librbd::journal::AioDiscardEvent( + 0, payload.size(), ictx->discard_granularity_bytes)); + inject_into_journal(ictx, + librbd::journal::AioDiscardEvent( + 0, payload.size(), ictx->discard_granularity_bytes)); + close_image(ictx); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 2, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_discard(aio_comp, 0, read_payload.size(), + ictx->discard_granularity_bytes); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); +} + +TEST_F(TestJournalReplay, AioWriteEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject a write operation into the journal + std::string payload(4096, '1'); + bufferlist payload_bl; + payload_bl.append(payload); + inject_into_journal(ictx, + librbd::journal::AioWriteEvent(0, payload.size(), payload_bl)); + close_image(ictx); + + // re-open the journal so that it replays the new entry + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + std::string read_payload(4096, '\0'); + librbd::io::ReadResult read_result{&read_payload[0], read_payload.size()}; + auto aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_read(aio_comp, 0, read_payload.size(), + std::move(read_result), 0); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + ASSERT_EQ(payload, read_payload); + + // check the commit position is properly updated + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + // replay several events and check the commit position + inject_into_journal(ictx, + librbd::journal::AioWriteEvent(0, payload.size(), payload_bl)); + inject_into_journal(ictx, + librbd::journal::AioWriteEvent(0, payload.size(), payload_bl)); + close_image(ictx); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 2, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_write(aio_comp, 0, payload.size(), + bufferlist{payload_bl}, 0); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); +} + +TEST_F(TestJournalReplay, AioFlushEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject a flush operation into the journal + inject_into_journal(ictx, librbd::journal::AioFlushEvent()); + close_image(ictx); + + // re-open the journal so that it replays the new entry + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // check the commit position is properly updated + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + // replay several events and check the commit position + inject_into_journal(ictx, librbd::journal::AioFlushEvent()); + inject_into_journal(ictx, librbd::journal::AioFlushEvent()); + close_image(ictx); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 2, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + auto aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_flush(aio_comp); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); +} + +TEST_F(TestJournalReplay, SnapCreate) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, librbd::journal::SnapCreateEvent(1, cls::rbd::UserSnapshotNamespace(), + "snap")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + { + RWLock::RLocker snap_locker(ictx->snap_lock); + ASSERT_NE(CEPH_NOSNAP, ictx->get_snap_id(cls::rbd::UserSnapshotNamespace(), + "snap")); + } + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap2")); +} + +TEST_F(TestJournalReplay, SnapProtect) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap")); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, + librbd::journal::SnapProtectEvent(1, + cls::rbd::UserSnapshotNamespace(), + "snap")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + + bool is_protected; + ASSERT_EQ(0, librbd::snap_is_protected(ictx, "snap", &is_protected)); + ASSERT_TRUE(is_protected); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap2")); + ASSERT_EQ(0, ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap2")); +} + +TEST_F(TestJournalReplay, SnapUnprotect) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap")); + uint64_t snap_id; + { + RWLock::RLocker snap_locker(ictx->snap_lock); + snap_id = ictx->get_snap_id(cls::rbd::UserSnapshotNamespace(), "snap"); + ASSERT_NE(CEPH_NOSNAP, snap_id); + } + ASSERT_EQ(0, ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap")); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, + librbd::journal::SnapUnprotectEvent(1, + cls::rbd::UserSnapshotNamespace(), + "snap")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + + bool is_protected; + ASSERT_EQ(0, librbd::snap_is_protected(ictx, "snap", &is_protected)); + ASSERT_FALSE(is_protected); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap2")); + ASSERT_EQ(0, ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap2")); + ASSERT_EQ(0, ictx->operations->snap_unprotect(cls::rbd::UserSnapshotNamespace(), + "snap2")); +} + +TEST_F(TestJournalReplay, SnapRename) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap")); + uint64_t snap_id; + { + RWLock::RLocker snap_locker(ictx->snap_lock); + snap_id = ictx->get_snap_id(cls::rbd::UserSnapshotNamespace(), "snap"); + ASSERT_NE(CEPH_NOSNAP, snap_id); + } + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, librbd::journal::SnapRenameEvent(1, snap_id, "snap", + "snap2")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + ASSERT_EQ(0, ictx->state->refresh()); + + { + RWLock::RLocker snap_locker(ictx->snap_lock); + snap_id = ictx->get_snap_id(cls::rbd::UserSnapshotNamespace(), "snap2"); + ASSERT_NE(CEPH_NOSNAP, snap_id); + } + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->snap_rename("snap2", "snap3")); +} + +TEST_F(TestJournalReplay, SnapRollback) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap")); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, + librbd::journal::SnapRollbackEvent(1, + cls::rbd::UserSnapshotNamespace(), + "snap")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + + // verify lock ordering constraints + librbd::NoOpProgressContext no_op_progress; + ASSERT_EQ(0, ictx->operations->snap_rollback(cls::rbd::UserSnapshotNamespace(), + "snap", + no_op_progress)); +} + +TEST_F(TestJournalReplay, SnapRemove) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap")); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, + librbd::journal::SnapRemoveEvent(1, + cls::rbd::UserSnapshotNamespace(), + "snap")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + + { + RWLock::RLocker snap_locker(ictx->snap_lock); + uint64_t snap_id = ictx->get_snap_id(cls::rbd::UserSnapshotNamespace(), + "snap"); + ASSERT_EQ(CEPH_NOSNAP, snap_id); + } + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap")); + ASSERT_EQ(0, ictx->operations->snap_remove(cls::rbd::UserSnapshotNamespace(), + "snap")); +} + +TEST_F(TestJournalReplay, Rename) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + std::string new_image_name(get_temp_image_name()); + inject_into_journal(ictx, librbd::journal::RenameEvent(1, new_image_name)); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + librbd::RBD rbd; + ASSERT_EQ(0, rbd.rename(m_ioctx, new_image_name.c_str(), m_image_name.c_str())); +} + +TEST_F(TestJournalReplay, Resize) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, librbd::journal::ResizeEvent(1, 16)); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + librbd::NoOpProgressContext no_op_progress; + ASSERT_EQ(0, ictx->operations->resize(0, true, no_op_progress)); +} + +TEST_F(TestJournalReplay, Flatten) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap")); + ASSERT_EQ(0, ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap")); + + std::string clone_name = get_temp_image_name(); + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap", m_ioctx, + clone_name.c_str(), ictx->features, &order, 0, 0)); + + librbd::ImageCtx *ictx2; + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + ASSERT_EQ(0, when_acquired_lock(ictx2)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx2, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx2, librbd::journal::FlattenEvent(1)); + inject_into_journal(ictx2, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx2); + + // replay journal + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + ASSERT_EQ(0, when_acquired_lock(ictx2)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx2, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + ASSERT_EQ(0, ictx->operations->snap_unprotect(cls::rbd::UserSnapshotNamespace(), + "snap")); + + // verify lock ordering constraints + librbd::NoOpProgressContext no_op; + ASSERT_EQ(-EINVAL, ictx2->operations->flatten(no_op)); +} + +TEST_F(TestJournalReplay, UpdateFeatures) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + uint64_t features = RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF; + bool enabled = !ictx->test_features(features); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject update_features op into journal + inject_into_journal(ictx, librbd::journal::UpdateFeaturesEvent(1, features, + enabled)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + ASSERT_EQ(enabled, ictx->test_features(features)); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->update_features(features, !enabled)); +} + +TEST_F(TestJournalReplay, MetadataSet) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject metadata_set op into journal + inject_into_journal(ictx, librbd::journal::MetadataSetEvent( + 1, "conf_rbd_mirroring_replay_delay", "9876")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + ASSERT_EQ(9876U, ictx->mirroring_replay_delay); + + std::string value; + ASSERT_EQ(0, librbd::metadata_get(ictx, "conf_rbd_mirroring_replay_delay", + &value)); + ASSERT_EQ("9876", value); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->metadata_set("key2", "value")); +} + +TEST_F(TestJournalReplay, MetadataRemove) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + ASSERT_EQ(0, ictx->operations->metadata_set( + "conf_rbd_mirroring_replay_delay", "9876")); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject metadata_remove op into journal + inject_into_journal(ictx, librbd::journal::MetadataRemoveEvent( + 1, "conf_rbd_mirroring_replay_delay")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + ASSERT_EQ(0U, ictx->mirroring_replay_delay); + + std::string value; + ASSERT_EQ(-ENOENT, + librbd::metadata_get(ictx, "conf_rbd_mirroring_replay_delay", + &value)); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->metadata_set("key", "value")); + ASSERT_EQ(0, ictx->operations->metadata_remove("key")); +} + +TEST_F(TestJournalReplay, ObjectPosition) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + std::string payload(4096, '1'); + bufferlist payload_bl; + payload_bl.append(payload); + auto aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_write(aio_comp, 0, payload.size(), + bufferlist{payload_bl}, 0); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_flush(aio_comp); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + // check the commit position updated + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + // write again + + aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_write(aio_comp, 0, payload.size(), + bufferlist{payload_bl}, 0); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + aio_comp = new librbd::io::AioCompletion(); + ictx->io_work_queue->aio_flush(aio_comp); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + // user flush requests are ignored when journaling + cache are enabled + C_SaferCond flush_ctx; + aio_comp = librbd::io::AioCompletion::create_and_start( + &flush_ctx, ictx, librbd::io::AIO_TYPE_FLUSH); + auto req = librbd::io::ImageDispatchSpec<>::create_flush_request( + *ictx, aio_comp, librbd::io::FLUSH_SOURCE_INTERNAL, {}); + req->send(); + delete req; + ASSERT_EQ(0, flush_ctx.wait()); + + // check the commit position updated + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(3, current_entry); +} diff --git a/src/test/librbd/journal/test_mock_OpenRequest.cc b/src/test/librbd/journal/test_mock_OpenRequest.cc new file mode 100644 index 00000000..866ab5be --- /dev/null +++ b/src/test/librbd/journal/test_mock_OpenRequest.cc @@ -0,0 +1,194 @@ +// -*- 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/journal/mock/MockJournaler.h" +#include "common/Mutex.h" +#include "cls/journal/cls_journal_types.h" +#include "librbd/journal/OpenRequest.h" +#include "librbd/journal/Types.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace journal { + +template <> +struct TypeTraits<MockTestImageCtx> { + typedef ::journal::MockJournaler Journaler; +}; + +} // namespace journal +} // namespace librbd + +// template definitions +#include "librbd/journal/OpenRequest.cc" +template class librbd::journal::OpenRequest<librbd::MockTestImageCtx>; + +namespace librbd { +namespace journal { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::WithArg; + +class TestMockJournalOpenRequest : public TestMockFixture { +public: + typedef OpenRequest<MockTestImageCtx> MockOpenRequest; + + TestMockJournalOpenRequest() : m_lock("m_lock") { + } + + void expect_init_journaler(::journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, init(_)) + .WillOnce(CompleteContext(r, static_cast<ContextWQ*>(NULL))); + } + + void expect_get_journaler_cached_client(::journal::MockJournaler &mock_journaler, + int r) { + journal::ImageClientMeta image_client_meta; + image_client_meta.tag_class = 345; + + journal::ClientData client_data; + client_data.client_meta = image_client_meta; + + cls::journal::Client client; + encode(client_data, client.data); + + EXPECT_CALL(mock_journaler, get_cached_client("", _)) + .WillOnce(DoAll(SetArgPointee<1>(client), + Return(r))); + } + + void expect_get_journaler_tags(MockImageCtx &mock_image_ctx, + ::journal::MockJournaler &mock_journaler, + int r) { + journal::TagData tag_data; + tag_data.mirror_uuid = "remote mirror"; + + bufferlist tag_data_bl; + encode(tag_data, tag_data_bl); + + ::journal::Journaler::Tags tags = {{0, 345, {}}, {1, 345, tag_data_bl}}; + EXPECT_CALL(mock_journaler, get_tags(345, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(tags), + WithArg<2>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)))); + } + + Mutex m_lock; + ImageClientMeta m_client_meta; + uint64_t m_tag_tid = 0; + TagData m_tag_data; +}; + +TEST_F(TestMockJournalOpenRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_init_journaler(mock_journaler, 0); + expect_get_journaler_cached_client(mock_journaler, 0); + expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0); + + C_SaferCond ctx; + auto req = MockOpenRequest::create(&mock_image_ctx, &mock_journaler, + &m_lock, &m_client_meta, &m_tag_tid, + &m_tag_data, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(345U, m_client_meta.tag_class); + ASSERT_EQ(1U, m_tag_tid); + ASSERT_EQ("remote mirror", m_tag_data.mirror_uuid); +} + +TEST_F(TestMockJournalOpenRequest, InitError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_init_journaler(mock_journaler, -ENOENT); + + C_SaferCond ctx; + auto req = MockOpenRequest::create(&mock_image_ctx, &mock_journaler, + &m_lock, &m_client_meta, &m_tag_tid, + &m_tag_data, &ctx); + req->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockJournalOpenRequest, GetCachedClientError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_init_journaler(mock_journaler, 0); + expect_get_journaler_cached_client(mock_journaler, -EINVAL); + + C_SaferCond ctx; + auto req = MockOpenRequest::create(&mock_image_ctx, &mock_journaler, + &m_lock, &m_client_meta, &m_tag_tid, + &m_tag_data, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockJournalOpenRequest, GetTagsError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_init_journaler(mock_journaler, 0); + expect_get_journaler_cached_client(mock_journaler, 0); + expect_get_journaler_tags(mock_image_ctx, mock_journaler, -EBADMSG); + + C_SaferCond ctx; + auto req = MockOpenRequest::create(&mock_image_ctx, &mock_journaler, + &m_lock, &m_client_meta, &m_tag_tid, + &m_tag_data, &ctx); + req->send(); + ASSERT_EQ(-EBADMSG, ctx.wait()); +} + +} // namespace journal +} // namespace librbd diff --git a/src/test/librbd/journal/test_mock_PromoteRequest.cc b/src/test/librbd/journal/test_mock_PromoteRequest.cc new file mode 100644 index 00000000..68a627a7 --- /dev/null +++ b/src/test/librbd/journal/test_mock_PromoteRequest.cc @@ -0,0 +1,356 @@ +// -*- 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/journal/mock/MockJournaler.h" +#include "librbd/journal/OpenRequest.h" +#include "librbd/journal/PromoteRequest.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace journal { + +template <> +struct TypeTraits<MockTestImageCtx> { + typedef ::journal::MockJournalerProxy Journaler; + typedef ::journal::MockFutureProxy Future; +}; + +template <> +struct OpenRequest<MockTestImageCtx> { + Context *on_finish = nullptr; + static OpenRequest *s_instance; + static OpenRequest *create(MockTestImageCtx *image_ctx, + ::journal::MockJournalerProxy *journaler, + Mutex *lock, ImageClientMeta *client_meta, + uint64_t *tag_tid, journal::TagData *tag_data, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + client_meta->tag_class = 456; + tag_data->mirror_uuid = Journal<>::ORPHAN_MIRROR_UUID; + *tag_tid = 567; + s_instance->on_finish = on_finish; + return s_instance; + } + + OpenRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +OpenRequest<MockTestImageCtx> *OpenRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace journal +} // namespace librbd + +// template definitions +#include "librbd/journal/PromoteRequest.cc" +template class librbd::journal::PromoteRequest<librbd::MockTestImageCtx>; + +namespace librbd { +namespace journal { + +using ::testing::_; +using ::testing::A; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::WithArg; + +class TestMockJournalPromoteRequest : public TestMockFixture { +public: + typedef PromoteRequest<MockTestImageCtx> MockPromoteRequest; + typedef OpenRequest<MockTestImageCtx> MockOpenRequest; + + void expect_construct_journaler(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, construct()); + } + + void expect_open_journaler(MockTestImageCtx &mock_image_ctx, + MockOpenRequest &mock_open_request, int r) { + EXPECT_CALL(mock_open_request, send()) + .WillOnce(FinishRequest(&mock_open_request, r, &mock_image_ctx)); + } + + void expect_allocate_tag(::journal::MockJournaler &mock_journaler, + const journal::TagPredecessor &predecessor, int r) { + TagData tag_data; + tag_data.mirror_uuid = Journal<>::LOCAL_MIRROR_UUID; + tag_data.predecessor = predecessor; + + bufferlist tag_data_bl; + using ceph::encode; + encode(tag_data, tag_data_bl); + + EXPECT_CALL(mock_journaler, allocate_tag(456, ContentsEqual(tag_data_bl), + _, _)) + .WillOnce(WithArg<3>(CompleteContext(r, static_cast<ContextWQ*>(NULL)))); + } + + void expect_append_journaler(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, append(_, _)) + .WillOnce(Return(::journal::MockFutureProxy())); + } + + void expect_future_flush(::journal::MockFuture &mock_future, int r) { + EXPECT_CALL(mock_future, flush(_)) + .WillOnce(CompleteContext(r, static_cast<ContextWQ*>(NULL))); + } + + void expect_future_committed(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, committed(A<const ::journal::MockFutureProxy &>())); + } + + void expect_flush_commit_position(::journal::MockJournaler &mock_journaler, + int r) { + EXPECT_CALL(mock_journaler, flush_commit_position(_)) + .WillOnce(CompleteContext(r, static_cast<ContextWQ*>(NULL))); + } + + void expect_start_append(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, start_append(_)); + } + + void expect_stop_append(::journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, stop_append(_)) + .WillOnce(CompleteContext(r, static_cast<ContextWQ*>(NULL))); + } + + void expect_shut_down_journaler(::journal::MockJournaler &mock_journaler, + int r) { + EXPECT_CALL(mock_journaler, shut_down(_)) + .WillOnce(CompleteContext(r, static_cast<ContextWQ*>(NULL))); + } + +}; + +TEST_F(TestMockJournalPromoteRequest, SuccessOrderly) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::ORPHAN_MIRROR_UUID, true, 567, 1}, 0); + + ::journal::MockFuture mock_future; + expect_start_append(mock_journaler); + expect_append_journaler(mock_journaler); + expect_future_flush(mock_future, 0); + expect_future_committed(mock_journaler); + expect_flush_commit_position(mock_journaler, 0); + expect_stop_append(mock_journaler, 0); + + expect_shut_down_journaler(mock_journaler, 0); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, SuccessForced) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::LOCAL_MIRROR_UUID, true, 567, 0}, 0); + + ::journal::MockFuture mock_future; + expect_start_append(mock_journaler); + expect_append_journaler(mock_journaler); + expect_future_flush(mock_future, 0); + expect_future_committed(mock_journaler); + expect_flush_commit_position(mock_journaler, 0); + expect_stop_append(mock_journaler, 0); + + expect_shut_down_journaler(mock_journaler, 0); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, OpenError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, -ENOENT); + expect_shut_down_journaler(mock_journaler, -EINVAL); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, false, &ctx); + req->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, AllocateTagError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::LOCAL_MIRROR_UUID, true, 567, 0}, -EBADMSG); + expect_shut_down_journaler(mock_journaler, -EINVAL); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, true, &ctx); + req->send(); + ASSERT_EQ(-EBADMSG, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, AppendEventError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::ORPHAN_MIRROR_UUID, true, 567, 1}, 0); + + ::journal::MockFuture mock_future; + expect_start_append(mock_journaler); + expect_append_journaler(mock_journaler); + expect_future_flush(mock_future, -EPERM); + expect_stop_append(mock_journaler, 0); + + expect_shut_down_journaler(mock_journaler, 0); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, false, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, CommitEventError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::ORPHAN_MIRROR_UUID, true, 567, 1}, 0); + + ::journal::MockFuture mock_future; + expect_start_append(mock_journaler); + expect_append_journaler(mock_journaler); + expect_future_flush(mock_future, 0); + expect_future_committed(mock_journaler); + expect_flush_commit_position(mock_journaler, -EINVAL); + expect_stop_append(mock_journaler, 0); + + expect_shut_down_journaler(mock_journaler, 0); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, ShutDownError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::LOCAL_MIRROR_UUID, true, 567, 0}, 0); + + ::journal::MockFuture mock_future; + expect_start_append(mock_journaler); + expect_append_journaler(mock_journaler); + expect_future_flush(mock_future, 0); + expect_future_committed(mock_journaler); + expect_flush_commit_position(mock_journaler, 0); + expect_stop_append(mock_journaler, 0); + + expect_shut_down_journaler(mock_journaler, -EINVAL); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, true, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace journal +} // namespace librbd diff --git a/src/test/librbd/journal/test_mock_Replay.cc b/src/test/librbd/journal/test_mock_Replay.cc new file mode 100644 index 00000000..62014bab --- /dev/null +++ b/src/test/librbd/journal/test_mock_Replay.cc @@ -0,0 +1,2080 @@ +// -*- 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 "librbd/io/ImageRequest.h" +#include "librbd/journal/Replay.h" +#include "librbd/journal/Types.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <boost/scope_exit.hpp> + +namespace librbd { + +namespace { + +struct MockReplayImageCtx : public MockImageCtx { + explicit MockReplayImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace io { + +template <> +struct ImageRequest<MockReplayImageCtx> { + static ImageRequest *s_instance; + + MOCK_METHOD4(aio_write, void(AioCompletion *c, const Extents &image_extents, + const bufferlist &bl, int op_flags)); + static void aio_write(MockReplayImageCtx *ictx, AioCompletion *c, + Extents &&image_extents, bufferlist &&bl, + int op_flags, const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_write(c, image_extents, bl, op_flags); + } + + MOCK_METHOD3(aio_discard, void(AioCompletion *c, const Extents& image_extents, + uint32_t discard_granularity_bytes)); + static void aio_discard(MockReplayImageCtx *ictx, AioCompletion *c, + Extents&& image_extents, + uint32_t discard_granularity_bytes, + const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_discard(c, image_extents, discard_granularity_bytes); + } + + MOCK_METHOD1(aio_flush, void(AioCompletion *c)); + static void aio_flush(MockReplayImageCtx *ictx, AioCompletion *c, + FlushSource, const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_flush(c); + } + + MOCK_METHOD4(aio_writesame, void(AioCompletion *c, + const Extents& image_extents, + const bufferlist &bl, int op_flags)); + static void aio_writesame(MockReplayImageCtx *ictx, AioCompletion *c, + Extents&& image_extents, bufferlist &&bl, + int op_flags, const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_writesame(c, image_extents, bl, op_flags); + } + + MOCK_METHOD6(aio_compare_and_write, void(AioCompletion *c, const Extents &image_extents, + const bufferlist &cmp_bl, const bufferlist &bl, + uint64_t *mismatch_offset, int op_flags)); + static void aio_compare_and_write(MockReplayImageCtx *ictx, AioCompletion *c, + Extents &&image_extents, bufferlist &&cmp_bl, + bufferlist &&bl, uint64_t *mismatch_offset, + int op_flags, const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_compare_and_write(c, image_extents, cmp_bl, bl, + mismatch_offset, op_flags); + } + + ImageRequest() { + s_instance = this; + } +}; + +ImageRequest<MockReplayImageCtx> *ImageRequest<MockReplayImageCtx>::s_instance = nullptr; + +} // namespace io + +namespace util { + +inline ImageCtx *get_image_ctx(librbd::MockReplayImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util + +} // namespace librbd + +// template definitions +#include "librbd/journal/Replay.cc" +template class librbd::journal::Replay<librbd::MockReplayImageCtx>; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::StrEq; +using ::testing::WithArgs; + +MATCHER_P(BufferlistEqual, str, "") { + bufferlist bl(arg); + return (strncmp(bl.c_str(), str, strlen(str)) == 0); +} + +MATCHER_P(CStrEq, str, "") { + return (strncmp(arg, str, strlen(str)) == 0); +} + +ACTION_P2(NotifyInvoke, lock, cond) { + Mutex::Locker locker(*lock); + cond->Signal(); +} + +ACTION_P2(CompleteAioCompletion, r, image_ctx) { + image_ctx->op_work_queue->queue(new FunctionContext([this, arg0](int r) { + arg0->get(); + arg0->init_time(image_ctx, librbd::io::AIO_TYPE_NONE); + arg0->set_request_count(1); + arg0->complete_request(r); + }), r); +} + +namespace librbd { +namespace journal { + +class TestMockJournalReplay : public TestMockFixture { +public: + typedef io::ImageRequest<MockReplayImageCtx> MockIoImageRequest; + typedef Replay<MockReplayImageCtx> MockJournalReplay; + + TestMockJournalReplay() : m_invoke_lock("m_invoke_lock") { + } + + void expect_accept_ops(MockExclusiveLock &mock_exclusive_lock, bool accept) { + EXPECT_CALL(mock_exclusive_lock, accept_ops()).WillRepeatedly( + Return(accept)); + } + + void expect_aio_discard(MockIoImageRequest &mock_io_image_request, + io::AioCompletion **aio_comp, uint64_t off, + uint64_t len, uint32_t discard_granularity_bytes) { + EXPECT_CALL(mock_io_image_request, aio_discard(_, io::Extents{{off, len}}, + discard_granularity_bytes)) + .WillOnce(SaveArg<0>(aio_comp)); + } + + void expect_aio_flush(MockIoImageRequest &mock_io_image_request, + io::AioCompletion **aio_comp) { + EXPECT_CALL(mock_io_image_request, aio_flush(_)) + .WillOnce(SaveArg<0>(aio_comp)); + } + + void expect_aio_flush(MockReplayImageCtx &mock_image_ctx, + MockIoImageRequest &mock_io_image_request, int r) { + EXPECT_CALL(mock_io_image_request, aio_flush(_)) + .WillOnce(CompleteAioCompletion(r, mock_image_ctx.image_ctx)); + } + + void expect_aio_write(MockIoImageRequest &mock_io_image_request, + io::AioCompletion **aio_comp, uint64_t off, + uint64_t len, const char *data) { + EXPECT_CALL(mock_io_image_request, + aio_write(_, io::Extents{{off, len}}, BufferlistEqual(data), _)) + .WillOnce(SaveArg<0>(aio_comp)); + } + + void expect_aio_writesame(MockIoImageRequest &mock_io_image_request, + io::AioCompletion **aio_comp, uint64_t off, + uint64_t len, const char *data) { + EXPECT_CALL(mock_io_image_request, + aio_writesame(_, io::Extents{{off, len}}, + BufferlistEqual(data), _)) + .WillOnce(SaveArg<0>(aio_comp)); + } + + void expect_aio_compare_and_write(MockIoImageRequest &mock_io_image_request, + io::AioCompletion **aio_comp, uint64_t off, + uint64_t len, const char *cmp_data, + const char *data, + uint64_t *mismatch_offset) { + EXPECT_CALL(mock_io_image_request, + aio_compare_and_write(_, io::Extents{{off, len}}, + BufferlistEqual(cmp_data), + BufferlistEqual(data), + mismatch_offset, _)) + .WillOnce(SaveArg<0>(aio_comp)); + } + + void expect_flatten(MockReplayImageCtx &mock_image_ctx, Context **on_finish) { + EXPECT_CALL(*mock_image_ctx.operations, execute_flatten(_, _)) + .WillOnce(DoAll(SaveArg<1>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_rename(MockReplayImageCtx &mock_image_ctx, Context **on_finish, + const char *image_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_rename(StrEq(image_name), _)) + .WillOnce(DoAll(SaveArg<1>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_resize(MockReplayImageCtx &mock_image_ctx, Context **on_finish, + uint64_t size, uint64_t op_tid) { + EXPECT_CALL(*mock_image_ctx.operations, execute_resize(size, _, _, _, op_tid)) + .WillOnce(DoAll(SaveArg<3>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_create(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *snap_name, + uint64_t op_tid) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_create(_, StrEq(snap_name), _, + op_tid, false)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_remove(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *snap_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_remove(_, StrEq(snap_name), _)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_rename(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, uint64_t snap_id, + const char *snap_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_rename(snap_id, StrEq(snap_name), _)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_protect(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *snap_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_protect(_, StrEq(snap_name), _)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_unprotect(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *snap_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_unprotect(_, StrEq(snap_name), _)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_rollback(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *snap_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_rollback(_, StrEq(snap_name), _, _)) + .WillOnce(DoAll(SaveArg<3>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_update_features(MockReplayImageCtx &mock_image_ctx, Context **on_finish, + uint64_t features, bool enabled, uint64_t op_tid) { + EXPECT_CALL(*mock_image_ctx.operations, execute_update_features(features, enabled, _, op_tid)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_metadata_set(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *key, + const char *value) { + EXPECT_CALL(*mock_image_ctx.operations, execute_metadata_set(StrEq(key), + StrEq(value), _)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_metadata_remove(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *key) { + EXPECT_CALL(*mock_image_ctx.operations, execute_metadata_remove(StrEq(key), _)) + .WillOnce(DoAll(SaveArg<1>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_refresh_image(MockReplayImageCtx &mock_image_ctx, bool required, + int r) { + EXPECT_CALL(*mock_image_ctx.state, is_refresh_required()) + .WillOnce(Return(required)); + if (required) { + EXPECT_CALL(*mock_image_ctx.state, refresh(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + } + + void expect_writeback_cache_enabled(MockReplayImageCtx &mock_image_ctx, + bool enabled) { + EXPECT_CALL(mock_image_ctx, is_writeback_cache_enabled()) + .WillRepeatedly(Return(enabled)); + } + + void when_process(MockJournalReplay &mock_journal_replay, + EventEntry &&event_entry, Context *on_ready, + Context *on_safe) { + bufferlist bl; + encode(event_entry, bl); + + auto it = bl.cbegin(); + when_process(mock_journal_replay, &it, on_ready, on_safe); + } + + void when_process(MockJournalReplay &mock_journal_replay, + bufferlist::const_iterator *it, Context *on_ready, + Context *on_safe) { + EventEntry event_entry; + int r = mock_journal_replay.decode(it, &event_entry); + ASSERT_EQ(0, r); + + mock_journal_replay.process(event_entry, on_ready, on_safe); + } + + void when_complete(MockReplayImageCtx &mock_image_ctx, + io::AioCompletion *aio_comp, int r) { + aio_comp->get(); + aio_comp->init_time(mock_image_ctx.image_ctx, librbd::io::AIO_TYPE_NONE); + aio_comp->set_request_count(1); + aio_comp->complete_request(r); + } + + int when_flush(MockJournalReplay &mock_journal_replay) { + C_SaferCond ctx; + mock_journal_replay.flush(&ctx); + return ctx.wait(); + } + + int when_shut_down(MockJournalReplay &mock_journal_replay, bool cancel_ops) { + C_SaferCond ctx; + mock_journal_replay.shut_down(cancel_ops, &ctx); + return ctx.wait(); + } + + void when_replay_op_ready(MockJournalReplay &mock_journal_replay, + uint64_t op_tid, Context *on_resume) { + mock_journal_replay.replay_op_ready(op_tid, on_resume); + } + + void wait_for_op_invoked(Context **on_finish, int r) { + { + Mutex::Locker locker(m_invoke_lock); + while (*on_finish == nullptr) { + m_invoke_cond.Wait(m_invoke_lock); + } + } + (*on_finish)->complete(r); + } + + bufferlist to_bl(const std::string &str) { + bufferlist bl; + bl.append(str); + return bl; + } + + Mutex m_invoke_lock; + Cond m_invoke_cond; +}; + +TEST_F(TestMockJournalReplay, AioDiscard) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_writeback_cache_enabled(mock_image_ctx, true); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_discard(mock_io_image_request, &aio_comp, 123, 456, + ictx->discard_granularity_bytes); + when_process(mock_journal_replay, + EventEntry{AioDiscardEvent(123, 456, + ictx->discard_granularity_bytes)}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, AioWrite) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_writeback_cache_enabled(mock_image_ctx, true); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_write(mock_io_image_request, &aio_comp, 123, 456, "test"); + when_process(mock_journal_replay, + EventEntry{AioWriteEvent(123, 456, to_bl("test"))}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, AioFlush) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_flush(mock_io_image_request, &aio_comp); + when_process(mock_journal_replay, EventEntry{AioFlushEvent()}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_safe.wait()); + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + ASSERT_EQ(0, on_ready.wait()); +} + +TEST_F(TestMockJournalReplay, AioWriteSame) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_writeback_cache_enabled(mock_image_ctx, true); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_writesame(mock_io_image_request, &aio_comp, 123, 456, "333"); + when_process(mock_journal_replay, + EventEntry{AioWriteSameEvent(123, 456, to_bl("333"))}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); +} + + +TEST_F(TestMockJournalReplay, AioCompareAndWrite) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_write_journal_replay(mock_image_ctx); + MockJournalReplay mock_compare_and_write_journal_replay(mock_image_ctx); + MockJournalReplay mock_mis_compare_and_write_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_writeback_cache_enabled(mock_image_ctx, true); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_write(mock_io_image_request, &aio_comp, 512, 512, "test"); + when_process(mock_write_journal_replay, + EventEntry{AioWriteEvent(512, 512, to_bl("test"))}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_write_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); + + expect_aio_compare_and_write(mock_io_image_request, &aio_comp, + 512, 512, "test", "test", nullptr); + when_process(mock_compare_and_write_journal_replay, + EventEntry{AioCompareAndWriteEvent(512, 512, to_bl("test"), + to_bl("test"))}, &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_compare_and_write_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); + + expect_aio_compare_and_write(mock_io_image_request, &aio_comp, + 512, 512, "111", "test", nullptr); + when_process(mock_mis_compare_and_write_journal_replay, + EventEntry{AioCompareAndWriteEvent(512, 512, to_bl("111"), + to_bl("test"))}, &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_mis_compare_and_write_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); + +} + +TEST_F(TestMockJournalReplay, IOError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_writeback_cache_enabled(mock_image_ctx, true); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_discard(mock_io_image_request, &aio_comp, 123, 456, + ictx->discard_granularity_bytes); + when_process(mock_journal_replay, + EventEntry{AioDiscardEvent(123, 456, + ictx->discard_granularity_bytes)}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, -EINVAL); + ASSERT_EQ(-EINVAL, on_safe.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + ASSERT_EQ(0, on_ready.wait()); +} + +TEST_F(TestMockJournalReplay, SoftFlushIO) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_writeback_cache_enabled(mock_image_ctx, true); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + const size_t io_count = 32; + C_SaferCond on_safes[io_count]; + for (size_t i = 0; i < io_count; ++i) { + io::AioCompletion *aio_comp; + io::AioCompletion *flush_comp = nullptr; + C_SaferCond on_ready; + expect_aio_discard(mock_io_image_request, &aio_comp, 123, 456, + ictx->discard_granularity_bytes); + if (i == io_count - 1) { + expect_aio_flush(mock_io_image_request, &flush_comp); + } + when_process(mock_journal_replay, + EventEntry{AioDiscardEvent(123, 456, + ictx->discard_granularity_bytes)}, + &on_ready, &on_safes[i]); + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + if (flush_comp != nullptr) { + when_complete(mock_image_ctx, flush_comp, 0); + } + } + for (auto &on_safe : on_safes) { + ASSERT_EQ(0, on_safe.wait()); + } + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); +} + +TEST_F(TestMockJournalReplay, PauseIO) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_writeback_cache_enabled(mock_image_ctx, true); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + const size_t io_count = 64; + std::list<io::AioCompletion *> flush_comps; + C_SaferCond on_safes[io_count]; + for (size_t i = 0; i < io_count; ++i) { + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + expect_aio_write(mock_io_image_request, &aio_comp, 123, 456, "test"); + if ((i + 1) % 32 == 0) { + flush_comps.push_back(nullptr); + expect_aio_flush(mock_io_image_request, &flush_comps.back()); + } + when_process(mock_journal_replay, + EventEntry{AioWriteEvent(123, 456, to_bl("test"))}, + &on_ready, &on_safes[i]); + when_complete(mock_image_ctx, aio_comp, 0); + if (i < io_count - 1) { + ASSERT_EQ(0, on_ready.wait()); + } else { + for (auto flush_comp : flush_comps) { + when_complete(mock_image_ctx, flush_comp, 0); + } + ASSERT_EQ(0, on_ready.wait()); + } + } + for (auto &on_safe : on_safes) { + ASSERT_EQ(0, on_safe.wait()); + } + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); +} + +TEST_F(TestMockJournalReplay, Flush) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_writeback_cache_enabled(mock_image_ctx, true); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp = nullptr; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_discard(mock_io_image_request, &aio_comp, 123, 456, + ictx->discard_granularity_bytes); + when_process(mock_journal_replay, + EventEntry{AioDiscardEvent(123, 456, + ictx->discard_granularity_bytes)}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_flush(mock_journal_replay)); + ASSERT_EQ(0, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, OpFinishError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, -EIO)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(-EIO, on_start_safe.wait()); + ASSERT_EQ(-EIO, on_finish_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); +} + +TEST_F(TestMockJournalReplay, BlockedOpFinishError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_create(mock_image_ctx, &on_finish, "snap", 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapCreateEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, -EBADMSG)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(-EBADMSG, on_resume.wait()); + wait_for_op_invoked(&on_finish, -ESTALE); + + ASSERT_EQ(-ESTALE, on_start_safe.wait()); + ASSERT_EQ(-ESTALE, on_finish_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); +} + +TEST_F(TestMockJournalReplay, MissingOpFinishEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + EXPECT_CALL(*mock_image_ctx.state, is_refresh_required()) + .WillRepeatedly(Return(false)); + + InSequence seq; + Context *on_snap_create_finish = nullptr; + expect_snap_create(mock_image_ctx, &on_snap_create_finish, "snap", 123); + + Context *on_snap_remove_finish = nullptr; + expect_snap_remove(mock_image_ctx, &on_snap_remove_finish, "snap"); + + C_SaferCond on_snap_remove_ready; + C_SaferCond on_snap_remove_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(122, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_snap_remove_ready, + &on_snap_remove_safe); + ASSERT_EQ(0, on_snap_remove_ready.wait()); + + C_SaferCond on_snap_create_ready; + C_SaferCond on_snap_create_safe; + when_process(mock_journal_replay, + EventEntry{SnapCreateEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_snap_create_ready, + &on_snap_create_safe); + + C_SaferCond on_shut_down; + mock_journal_replay.shut_down(false, &on_shut_down); + + wait_for_op_invoked(&on_snap_remove_finish, 0); + ASSERT_EQ(0, on_snap_remove_safe.wait()); + + C_SaferCond on_snap_create_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_snap_create_resume); + ASSERT_EQ(0, on_snap_create_resume.wait()); + + wait_for_op_invoked(&on_snap_create_finish, 0); + ASSERT_EQ(0, on_snap_create_ready.wait()); + ASSERT_EQ(0, on_snap_create_safe.wait()); + + ASSERT_EQ(0, on_shut_down.wait()); +} + +TEST_F(TestMockJournalReplay, MissingOpFinishEventCancelOps) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_snap_create_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_create(mock_image_ctx, &on_snap_create_finish, "snap", 123); + + C_SaferCond on_snap_remove_ready; + C_SaferCond on_snap_remove_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(122, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_snap_remove_ready, + &on_snap_remove_safe); + ASSERT_EQ(0, on_snap_remove_ready.wait()); + + C_SaferCond on_snap_create_ready; + C_SaferCond on_snap_create_safe; + when_process(mock_journal_replay, + EventEntry{SnapCreateEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_snap_create_ready, + &on_snap_create_safe); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_snap_create_ready.wait()); + + C_SaferCond on_shut_down; + mock_journal_replay.shut_down(true, &on_shut_down); + + ASSERT_EQ(-ERESTART, on_resume.wait()); + on_snap_create_finish->complete(-ERESTART); + ASSERT_EQ(-ERESTART, on_snap_create_safe.wait()); + + ASSERT_EQ(-ERESTART, on_snap_remove_safe.wait()); + ASSERT_EQ(0, on_shut_down.wait()); +} + +TEST_F(TestMockJournalReplay, UnknownOpFinishEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_ready, &on_safe); + + ASSERT_EQ(0, on_safe.wait()); + ASSERT_EQ(0, on_ready.wait()); +} + +TEST_F(TestMockJournalReplay, OpEventError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_remove(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EINVAL); + ASSERT_EQ(-EINVAL, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(-EINVAL, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapCreateEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_create(mock_image_ctx, &on_finish, "snap", 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapCreateEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_resume.wait()); + wait_for_op_invoked(&on_finish, 0); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapCreateEventExists) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_create(mock_image_ctx, &on_finish, "snap", 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapCreateEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + + wait_for_op_invoked(&on_finish, -EEXIST); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapRemoveEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_remove(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapRemoveEventDNE) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_remove(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -ENOENT); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapRenameEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_rename(mock_image_ctx, &on_finish, 234, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRenameEvent(123, 234, "snap1", "snap")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapRenameEventExists) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_rename(mock_image_ctx, &on_finish, 234, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRenameEvent(123, 234, "snap1", "snap")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EEXIST); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapProtectEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_protect(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapProtectEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapProtectEventBusy) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_protect(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapProtectEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EBUSY); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapUnprotectEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_unprotect(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapUnprotectEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapUnprotectOpFinishBusy) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapUnprotectEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + // aborts the snap unprotect op if image had children + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, -EBUSY)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); +} + +TEST_F(TestMockJournalReplay, SnapUnprotectEventInvalid) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_unprotect(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapUnprotectEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EINVAL); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapRollbackEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_rollback(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRollbackEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, RenameEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_rename(mock_image_ctx, &on_finish, "image"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{RenameEvent(123, "image")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, RenameEventExists) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_rename(mock_image_ctx, &on_finish, "image"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{RenameEvent(123, "image")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EEXIST); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, ResizeEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_resize(mock_image_ctx, &on_finish, 234, 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{ResizeEvent(123, 234)}, + &on_start_ready, &on_start_safe); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_resume.wait()); + wait_for_op_invoked(&on_finish, 0); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, FlattenEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_flatten(mock_image_ctx, &on_finish); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{FlattenEvent(123)}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, FlattenEventInvalid) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_flatten(mock_image_ctx, &on_finish); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{FlattenEvent(123)}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EINVAL); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, UpdateFeaturesEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features = RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF; + bool enabled = !ictx->test_features(features); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_update_features(mock_image_ctx, &on_finish, features, enabled, 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{UpdateFeaturesEvent(123, features, enabled)}, + &on_start_ready, &on_start_safe); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_resume.wait()); + wait_for_op_invoked(&on_finish, 0); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, MetadataSetEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_metadata_set(mock_image_ctx, &on_finish, "key", "value"); + expect_refresh_image(mock_image_ctx, false, 0); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{MetadataSetEvent(123, "key", "value")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, MetadataRemoveEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_metadata_remove(mock_image_ctx, &on_finish, "key"); + expect_refresh_image(mock_image_ctx, false, 0); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{MetadataRemoveEvent(123, "key")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, MetadataRemoveEventDNE) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_metadata_remove(mock_image_ctx, &on_finish, "key"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{MetadataRemoveEvent(123, "key")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -ENOENT); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, UnknownEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + bufferlist bl; + ENCODE_START(1, 1, bl); + encode(static_cast<uint32_t>(-1), bl); + ENCODE_FINISH(bl); + + auto it = bl.cbegin(); + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, &it, &on_ready, &on_safe); + + ASSERT_EQ(0, on_safe.wait()); + ASSERT_EQ(0, on_ready.wait()); +} + +TEST_F(TestMockJournalReplay, RefreshImageBeforeOpStart) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, true, 0); + expect_resize(mock_image_ctx, &on_finish, 234, 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{ResizeEvent(123, 234)}, + &on_start_ready, &on_start_safe); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_resume.wait()); + wait_for_op_invoked(&on_finish, 0); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, FlushEventAfterShutDown) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, EventEntry{AioFlushEvent()}, + &on_ready, &on_safe); + ASSERT_EQ(0, on_ready.wait()); + ASSERT_EQ(-ESHUTDOWN, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, ModifyEventAfterShutDown) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, + EventEntry{AioWriteEvent(123, 456, to_bl("test"))}, + &on_ready, &on_safe); + ASSERT_EQ(0, on_ready.wait()); + ASSERT_EQ(-ESHUTDOWN, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, OpEventAfterShutDown) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, EventEntry{RenameEvent(123, "image")}, + &on_ready, &on_safe); + ASSERT_EQ(0, on_ready.wait()); + ASSERT_EQ(-ESHUTDOWN, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, LockLostBeforeProcess) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, false); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, EventEntry{AioFlushEvent()}, + &on_ready, &on_safe); + ASSERT_EQ(0, on_ready.wait()); + ASSERT_EQ(-ECANCELED, on_safe.wait()); + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); +} + +TEST_F(TestMockJournalReplay, LockLostBeforeExecuteOp) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, false); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_exclusive_lock, accept_ops()).WillOnce(Return(true)); + EXPECT_CALL(mock_exclusive_lock, accept_ops()).WillOnce(Return(true)); + expect_refresh_image(mock_image_ctx, false, 0); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{RenameEvent(123, "image")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(-ECANCELED, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(-ECANCELED, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, WritebackCacheDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_writeback_cache_enabled(mock_image_ctx, false); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_discard(mock_io_image_request, &aio_comp, 123, 456, 0); + when_process(mock_journal_replay, + EventEntry{AioDiscardEvent(123, 456, 0)}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + ASSERT_EQ(0, on_safe.wait()); + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); +} + +} // namespace journal +} // namespace librbd diff --git a/src/test/librbd/journal/test_mock_ResetRequest.cc b/src/test/librbd/journal/test_mock_ResetRequest.cc new file mode 100644 index 00000000..446acdaa --- /dev/null +++ b/src/test/librbd/journal/test_mock_ResetRequest.cc @@ -0,0 +1,264 @@ +// -*- 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/journal/mock/MockJournaler.h" +#include "common/Mutex.h" +#include "cls/journal/cls_journal_types.h" +#include "librbd/journal/CreateRequest.h" +#include "librbd/journal/RemoveRequest.h" +#include "librbd/journal/ResetRequest.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace journal { + +template <> +struct TypeTraits<MockTestImageCtx> { + typedef ::journal::MockJournalerProxy Journaler; +}; + +template <> +struct CreateRequest<MockTestImageCtx> { + static CreateRequest* s_instance; + Context* on_finish = nullptr; + + static CreateRequest* create(IoCtx &ioctx, const std::string &imageid, + uint8_t order, uint8_t splay_width, + const std::string &object_pool, + uint64_t tag_class, TagData &tag_data, + const std::string &client_id, + ContextWQ *op_work_queue, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + CreateRequest() { + s_instance = this; + } +}; + +template <> +struct RemoveRequest<MockTestImageCtx> { + static RemoveRequest* s_instance; + Context* on_finish = nullptr; + + static RemoveRequest* create(IoCtx &ioctx, const std::string &image_id, + const std::string &client_id, + ContextWQ *op_work_queue, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + RemoveRequest() { + s_instance = this; + } +}; + +CreateRequest<MockTestImageCtx>* CreateRequest<MockTestImageCtx>::s_instance = nullptr; +RemoveRequest<MockTestImageCtx>* RemoveRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace journal +} // namespace librbd + +#include "librbd/journal/ResetRequest.cc" + +namespace librbd { +namespace journal { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; + +class TestMockJournalResetRequest : public TestMockFixture { +public: + typedef ResetRequest<MockTestImageCtx> MockResetRequest; + typedef CreateRequest<MockTestImageCtx> MockCreateRequest; + typedef RemoveRequest<MockTestImageCtx> MockRemoveRequest; + + void expect_construct_journaler(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, construct()); + } + + void expect_init_journaler(::journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, init(_)) + .WillOnce(CompleteContext(r, static_cast<ContextWQ*>(NULL))); + } + + void expect_get_metadata(::journal::MockJournaler& mock_journaler) { + EXPECT_CALL(mock_journaler, get_metadata(_, _, _)) + .WillOnce(Invoke([](uint8_t* order, uint8_t* splay_width, + int64_t* pool_id) { + *order = 24; + *splay_width = 4; + *pool_id = -1; + })); + } + + void expect_shut_down_journaler(::journal::MockJournaler &mock_journaler, + int r) { + EXPECT_CALL(mock_journaler, shut_down(_)) + .WillOnce(CompleteContext(r, static_cast<ContextWQ*>(NULL))); + } + + void expect_remove(MockRemoveRequest& mock_remove_request, int r) { + EXPECT_CALL(mock_remove_request, send()) + .WillOnce(Invoke([&mock_remove_request, r]() { + mock_remove_request.on_finish->complete(r); + })); + } + + void expect_create(MockCreateRequest& mock_create_request, int r) { + EXPECT_CALL(mock_create_request, send()) + .WillOnce(Invoke([&mock_create_request, r]() { + mock_create_request.on_finish->complete(r); + })); + } +}; + +TEST_F(TestMockJournalResetRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + InSequence seq; + ::journal::MockJournaler mock_journaler; + expect_construct_journaler(mock_journaler); + expect_init_journaler(mock_journaler, 0); + expect_get_metadata(mock_journaler); + expect_shut_down_journaler(mock_journaler, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + C_SaferCond ctx; + auto req = MockResetRequest::create(m_ioctx, "image id", + Journal<>::IMAGE_CLIENT_ID, + Journal<>::LOCAL_MIRROR_UUID, + ictx->op_work_queue , &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockJournalResetRequest, InitError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + InSequence seq; + ::journal::MockJournaler mock_journaler; + expect_construct_journaler(mock_journaler); + expect_init_journaler(mock_journaler, -EINVAL); + expect_shut_down_journaler(mock_journaler, 0); + + C_SaferCond ctx; + auto req = MockResetRequest::create(m_ioctx, "image id", + Journal<>::IMAGE_CLIENT_ID, + Journal<>::LOCAL_MIRROR_UUID, + ictx->op_work_queue , &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockJournalResetRequest, ShutDownError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + InSequence seq; + ::journal::MockJournaler mock_journaler; + expect_construct_journaler(mock_journaler); + expect_init_journaler(mock_journaler, 0); + expect_get_metadata(mock_journaler); + expect_shut_down_journaler(mock_journaler, -EINVAL); + + C_SaferCond ctx; + auto req = MockResetRequest::create(m_ioctx, "image id", + Journal<>::IMAGE_CLIENT_ID, + Journal<>::LOCAL_MIRROR_UUID, + ictx->op_work_queue , &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockJournalResetRequest, RemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + InSequence seq; + ::journal::MockJournaler mock_journaler; + expect_construct_journaler(mock_journaler); + expect_init_journaler(mock_journaler, 0); + expect_get_metadata(mock_journaler); + expect_shut_down_journaler(mock_journaler, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, -EINVAL); + + C_SaferCond ctx; + auto req = MockResetRequest::create(m_ioctx, "image id", + Journal<>::IMAGE_CLIENT_ID, + Journal<>::LOCAL_MIRROR_UUID, + ictx->op_work_queue , &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockJournalResetRequest, CreateError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + InSequence seq; + ::journal::MockJournaler mock_journaler; + expect_construct_journaler(mock_journaler); + expect_init_journaler(mock_journaler, 0); + expect_get_metadata(mock_journaler); + expect_shut_down_journaler(mock_journaler, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, -EINVAL); + + C_SaferCond ctx; + auto req = MockResetRequest::create(m_ioctx, "image id", + Journal<>::IMAGE_CLIENT_ID, + Journal<>::LOCAL_MIRROR_UUID, + ictx->op_work_queue , &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace journal +} // namespace librbd diff --git a/src/test/librbd/managed_lock/test_mock_AcquireRequest.cc b/src/test/librbd/managed_lock/test_mock_AcquireRequest.cc new file mode 100644 index 00000000..5218fa96 --- /dev/null +++ b/src/test/librbd/managed_lock/test_mock_AcquireRequest.cc @@ -0,0 +1,270 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "cls/lock/cls_lock_ops.h" +#include "librbd/managed_lock/AcquireRequest.h" +#include "librbd/managed_lock/BreakRequest.h" +#include "librbd/managed_lock/GetLockerRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <list> + +namespace librbd { +namespace watcher { +template <> +struct Traits<MockImageCtx> { + typedef librbd::MockImageWatcher Watcher; +}; +} + +namespace managed_lock { + +template<> +struct BreakRequest<librbd::MockImageCtx> { + Context *on_finish = nullptr; + static BreakRequest *s_instance; + static BreakRequest* create(librados::IoCtx& ioctx, ContextWQ *work_queue, + const std::string& oid, const Locker &locker, + bool exclusive, bool blacklist_locker, + uint32_t blacklist_expire_seconds, + bool force_break_lock, Context *on_finish) { + CephContext *cct = reinterpret_cast<CephContext *>(ioctx.cct()); + EXPECT_EQ(cct->_conf.get_val<bool>("rbd_blacklist_on_break_lock"), + blacklist_locker); + EXPECT_EQ(cct->_conf.get_val<uint64_t>("rbd_blacklist_expire_seconds"), + blacklist_expire_seconds); + EXPECT_FALSE(force_break_lock); + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + BreakRequest() { + s_instance = this; + } + MOCK_METHOD0(send, void()); +}; + +template <> +struct GetLockerRequest<librbd::MockImageCtx> { + Locker *locker = nullptr; + Context *on_finish = nullptr; + + static GetLockerRequest *s_instance; + static GetLockerRequest* create(librados::IoCtx& ioctx, + const std::string& oid, bool exclusive, + Locker *locker, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->locker = locker; + s_instance->on_finish = on_finish; + return s_instance; + } + + GetLockerRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +BreakRequest<librbd::MockImageCtx> *BreakRequest<librbd::MockImageCtx>::s_instance = nullptr; +GetLockerRequest<librbd::MockImageCtx> *GetLockerRequest<librbd::MockImageCtx>::s_instance = nullptr; + +} // namespace managed_lock +} // namespace librbd + +// template definitions +#include "librbd/managed_lock/AcquireRequest.cc" +template class librbd::managed_lock::AcquireRequest<librbd::MockImageCtx>; + +namespace { + +MATCHER_P(IsLockType, exclusive, "") { + cls_lock_lock_op op; + bufferlist bl; + bl.share(arg); + auto iter = bl.cbegin(); + decode(op, iter); + return op.type == (exclusive ? LOCK_EXCLUSIVE : LOCK_SHARED); +} + +} // anonymous namespace + +namespace librbd { +namespace managed_lock { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +static const std::string TEST_COOKIE("auto 123"); + +class TestMockManagedLockAcquireRequest : public TestMockFixture { +public: + typedef AcquireRequest<MockImageCtx> MockAcquireRequest; + typedef BreakRequest<MockImageCtx> MockBreakRequest; + typedef GetLockerRequest<MockImageCtx> MockGetLockerRequest; + + void expect_lock(MockImageCtx &mock_image_ctx, int r, + bool exclusive = true) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("lock"), + StrEq("lock"), IsLockType(exclusive), _, _)) + .WillOnce(Return(r)); + } + + void expect_get_locker(MockImageCtx &mock_image_ctx, + MockGetLockerRequest &mock_get_locker_request, + const Locker &locker, int r) { + EXPECT_CALL(mock_get_locker_request, send()) + .WillOnce(Invoke([&mock_image_ctx, &mock_get_locker_request, locker, r]() { + *mock_get_locker_request.locker = locker; + mock_image_ctx.image_ctx->op_work_queue->queue( + mock_get_locker_request.on_finish, r); + })); + } + + void expect_break_lock(MockImageCtx &mock_image_ctx, + MockBreakRequest &mock_break_request, int r) { + EXPECT_CALL(mock_break_request, send()) + .WillOnce(FinishRequest(&mock_break_request, r, &mock_image_ctx)); + } +}; + +TEST_F(TestMockManagedLockAcquireRequest, SuccessExclusive) { + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockGetLockerRequest mock_get_locker_request; + + InSequence seq; + expect_get_locker(mock_image_ctx, mock_get_locker_request, {}, 0); + expect_lock(mock_image_ctx, 0); + + C_SaferCond ctx; + MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.image_watcher, ictx->op_work_queue, mock_image_ctx.header_oid, + TEST_COOKIE, true, true, 0, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockManagedLockAcquireRequest, SuccessShared) { + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockGetLockerRequest mock_get_locker_request; + + InSequence seq; + expect_get_locker(mock_image_ctx, mock_get_locker_request, {}, 0); + expect_lock(mock_image_ctx, 0, false); + + C_SaferCond ctx; + MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.image_watcher, ictx->op_work_queue, mock_image_ctx.header_oid, + TEST_COOKIE, false, true, 0, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockManagedLockAcquireRequest, LockBusy) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockGetLockerRequest mock_get_locker_request; + MockBreakRequest mock_break_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}, + 0); + expect_lock(mock_image_ctx, -EBUSY); + expect_break_lock(mock_image_ctx, mock_break_request, 0); + expect_lock(mock_image_ctx, -ENOENT); + + C_SaferCond ctx; + MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.image_watcher, ictx->op_work_queue, mock_image_ctx.header_oid, + TEST_COOKIE, true, true, 0, &ctx); + req->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockManagedLockAcquireRequest, GetLockInfoError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockGetLockerRequest mock_get_locker_request; + + InSequence seq; + expect_get_locker(mock_image_ctx, mock_get_locker_request, {}, -EINVAL); + + C_SaferCond ctx; + MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.image_watcher, ictx->op_work_queue, mock_image_ctx.header_oid, + TEST_COOKIE, true, true, 0, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockManagedLockAcquireRequest, GetLockInfoEmpty) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockGetLockerRequest mock_get_locker_request; + + InSequence seq; + expect_get_locker(mock_image_ctx, mock_get_locker_request, {}, -ENOENT); + expect_lock(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.image_watcher, ictx->op_work_queue, mock_image_ctx.header_oid, + TEST_COOKIE, true, true, 0, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockManagedLockAcquireRequest, BreakLockError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockGetLockerRequest mock_get_locker_request; + MockBreakRequest mock_break_request; + + InSequence seq; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}, + 0); + expect_lock(mock_image_ctx, -EBUSY); + expect_break_lock(mock_image_ctx, mock_break_request, -EINVAL); + + C_SaferCond ctx; + MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.image_watcher, ictx->op_work_queue, mock_image_ctx.header_oid, + TEST_COOKIE, true, true, 0, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace managed_lock +} // namespace librbd diff --git a/src/test/librbd/managed_lock/test_mock_BreakRequest.cc b/src/test/librbd/managed_lock/test_mock_BreakRequest.cc new file mode 100644 index 00000000..efab85e9 --- /dev/null +++ b/src/test/librbd/managed_lock/test_mock_BreakRequest.cc @@ -0,0 +1,471 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "cls/lock/cls_lock_ops.h" +#include "librbd/managed_lock/BreakRequest.h" +#include "librbd/managed_lock/GetLockerRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <list> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace managed_lock { + +template <> +struct GetLockerRequest<librbd::MockTestImageCtx> { + Locker *locker; + Context *on_finish = nullptr; + static GetLockerRequest *s_instance; + static GetLockerRequest* create(librados::IoCtx& ioctx, + const std::string& oid, bool exclusive, + Locker *locker, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->locker = locker; + s_instance->on_finish = on_finish; + return s_instance; + } + + + GetLockerRequest() { + s_instance = this; + } + MOCK_METHOD0(send, void()); +}; + +GetLockerRequest<librbd::MockTestImageCtx> *GetLockerRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace managed_lock +} // namespace librbd + +// template definitions +#include "librbd/managed_lock/BreakRequest.cc" + +namespace librbd { +namespace managed_lock { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockManagedLockBreakRequest : public TestMockFixture { +public: + typedef BreakRequest<MockTestImageCtx> MockBreakRequest; + typedef GetLockerRequest<MockTestImageCtx> MockGetLockerRequest; + + void expect_list_watchers(MockTestImageCtx &mock_image_ctx, int r, + const std::string &address, uint64_t watch_handle) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + list_watchers(mock_image_ctx.header_oid, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + obj_watch_t watcher; + strcpy(watcher.addr, (address + ":0/0").c_str()); + watcher.watcher_id = 0; + watcher.cookie = watch_handle; + + std::list<obj_watch_t> watchers; + watchers.push_back(watcher); + + expect.WillOnce(DoAll(SetArgPointee<1>(watchers), Return(0))); + } + } + + void expect_get_locker(MockImageCtx &mock_image_ctx, + MockGetLockerRequest &mock_get_locker_request, + const Locker &locker, int r) { + EXPECT_CALL(mock_get_locker_request, send()) + .WillOnce(Invoke([&mock_image_ctx, &mock_get_locker_request, locker, r]() { + *mock_get_locker_request.locker = locker; + mock_image_ctx.image_ctx->op_work_queue->queue( + mock_get_locker_request.on_finish, r); + })); + } + + + void expect_blacklist_add(MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*get_mock_io_ctx(mock_image_ctx.md_ctx).get_mock_rados_client(), + blacklist_add(_, _)) + .WillOnce(Return(r)); + } + + void expect_break_lock(MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("lock"), StrEq("break_lock"), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_get_instance_id(MockTestImageCtx &mock_image_ctx, uint64_t id) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), get_instance_id()) + .WillOnce(Return(id)); + } +}; + +TEST_F(TestMockManagedLockBreakRequest, DeadLockOwner) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "dead client", 123); + + MockGetLockerRequest mock_get_locker_request; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}, + 0); + + expect_blacklist_add(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, 0); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, true, 0, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, ForceBreak) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "1.2.3.4", 123); + + MockGetLockerRequest mock_get_locker_request; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}, + 0); + + expect_blacklist_add(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, 0); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, true, 0, true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, GetWatchersError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, -EINVAL, "dead client", 123); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, true, 0, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, GetWatchersAlive) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "1.2.3.4", 123); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, true, 0, false, &ctx); + req->send(); + ASSERT_EQ(-EAGAIN, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, GetLockerUpdated) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "dead client", 123); + + MockGetLockerRequest mock_get_locker_request; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(2), "auto 123", "1.2.3.4:0/0", 123}, + 0); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, false, 0, false, &ctx); + req->send(); + ASSERT_EQ(-EAGAIN, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, GetLockerBusy) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "dead client", 123); + + MockGetLockerRequest mock_get_locker_request; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}, + -EBUSY); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, false, 0, false, &ctx); + req->send(); + ASSERT_EQ(-EAGAIN, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, GetLockerMissing) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "dead client", 123); + + MockGetLockerRequest mock_get_locker_request; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}, + -ENOENT); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, false, 0, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, GetLockerError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "dead client", 123); + + MockGetLockerRequest mock_get_locker_request; + expect_get_locker(mock_image_ctx, mock_get_locker_request, {}, -EINVAL); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, false, 0, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, BlacklistDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "dead client", 123); + + MockGetLockerRequest mock_get_locker_request; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}, + 0); + + expect_break_lock(mock_image_ctx, 0); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, false, 0, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, BlacklistSelf) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "dead client", 456); + + MockGetLockerRequest mock_get_locker_request; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(456), "auto 123", "1.2.3.4:0/0", + 123}, 0); + + expect_get_instance_id(mock_image_ctx, 456); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(456), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, true, 0, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, BlacklistError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "dead client", 123); + + MockGetLockerRequest mock_get_locker_request; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}, + 0); + + expect_blacklist_add(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, true, 0, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, BreakLockMissing) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "dead client", 123); + + MockGetLockerRequest mock_get_locker_request; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}, + 0); + + expect_blacklist_add(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, -ENOENT); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, true, 0, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockManagedLockBreakRequest, BreakLockError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_list_watchers(mock_image_ctx, 0, "dead client", 123); + + MockGetLockerRequest mock_get_locker_request; + expect_get_locker(mock_image_ctx, mock_get_locker_request, + {entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}, + 0); + + expect_blacklist_add(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + Locker locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + MockBreakRequest *req = MockBreakRequest::create( + mock_image_ctx.md_ctx, ictx->op_work_queue, mock_image_ctx.header_oid, + locker, true, true, 0, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace managed_lock +} // namespace librbd + diff --git a/src/test/librbd/managed_lock/test_mock_GetLockerRequest.cc b/src/test/librbd/managed_lock/test_mock_GetLockerRequest.cc new file mode 100644 index 00000000..69e987e4 --- /dev/null +++ b/src/test/librbd/managed_lock/test_mock_GetLockerRequest.cc @@ -0,0 +1,265 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "cls/lock/cls_lock_ops.h" +#include "librbd/managed_lock/GetLockerRequest.h" +#include "librbd/managed_lock/Types.h" +#include "librbd/managed_lock/Utils.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <list> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/managed_lock/GetLockerRequest.cc" + +namespace librbd { +namespace managed_lock { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockManagedLockGetLockerRequest : public TestMockFixture { +public: + typedef GetLockerRequest<MockTestImageCtx> MockGetLockerRequest; + + void expect_get_lock_info(MockTestImageCtx &mock_image_ctx, int r, + const entity_name_t &locker_entity, + const std::string &locker_address, + const std::string &locker_cookie, + const std::string &lock_tag, + ClsLockType lock_type) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("lock"), + StrEq("get_info"), _, _, _)); + if (r < 0 && r != -ENOENT) { + expect.WillOnce(Return(r)); + } else { + entity_name_t entity(locker_entity); + entity_addr_t entity_addr; + entity_addr.parse(locker_address.c_str(), NULL); + + cls_lock_get_info_reply reply; + if (r != -ENOENT) { + reply.lockers.emplace( + rados::cls::lock::locker_id_t(entity, locker_cookie), + rados::cls::lock::locker_info_t(utime_t(), entity_addr, "")); + reply.tag = lock_tag; + reply.lock_type = lock_type; + } + + bufferlist bl; + encode(reply, bl, CEPH_FEATURES_SUPPORTED_DEFAULT); + + std::string str(bl.c_str(), bl.length()); + expect.WillOnce(DoAll(WithArg<5>(CopyInBufferlist(str)), Return(0))); + } + } +}; + +TEST_F(TestMockManagedLockGetLockerRequest, SuccessExclusive) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_lock_info(mock_image_ctx, 0, entity_name_t::CLIENT(1), "1.2.3.4", + "auto 123", util::get_watcher_lock_tag(), + LOCK_EXCLUSIVE); + + C_SaferCond ctx; + Locker locker; + MockGetLockerRequest *req = MockGetLockerRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, true, &locker, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_EQ(entity_name_t::CLIENT(1), locker.entity); + ASSERT_EQ("1.2.3.4:0/0", locker.address); + ASSERT_EQ("auto 123", locker.cookie); + ASSERT_EQ(123U, locker.handle); +} + +TEST_F(TestMockManagedLockGetLockerRequest, SuccessShared) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_lock_info(mock_image_ctx, 0, entity_name_t::CLIENT(1), "1.2.3.4", + "auto 123", util::get_watcher_lock_tag(), + LOCK_SHARED); + + C_SaferCond ctx; + Locker locker; + MockGetLockerRequest *req = MockGetLockerRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, false, &locker, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_EQ(entity_name_t::CLIENT(1), locker.entity); + ASSERT_EQ("1.2.3.4:0/0", locker.address); + ASSERT_EQ("auto 123", locker.cookie); + ASSERT_EQ(123U, locker.handle); +} + +TEST_F(TestMockManagedLockGetLockerRequest, GetLockInfoError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_lock_info(mock_image_ctx, -EINVAL, entity_name_t::CLIENT(1), "", + "", "", LOCK_EXCLUSIVE); + + C_SaferCond ctx; + Locker locker; + MockGetLockerRequest *req = MockGetLockerRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, true, &locker, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockManagedLockGetLockerRequest, GetLockInfoEmpty) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_lock_info(mock_image_ctx, -ENOENT, entity_name_t::CLIENT(1), "", + "", "", LOCK_EXCLUSIVE); + + C_SaferCond ctx; + Locker locker; + MockGetLockerRequest *req = MockGetLockerRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, true, &locker, &ctx); + req->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockManagedLockGetLockerRequest, GetLockInfoExternalTag) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_lock_info(mock_image_ctx, 0, entity_name_t::CLIENT(1), "1.2.3.4", + "auto 123", "external tag", LOCK_EXCLUSIVE); + + C_SaferCond ctx; + Locker locker; + MockGetLockerRequest *req = MockGetLockerRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, true, &locker, &ctx); + req->send(); + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockManagedLockGetLockerRequest, GetLockInfoIncompatibleShared) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_lock_info(mock_image_ctx, 0, entity_name_t::CLIENT(1), "1.2.3.4", + "auto 123", util::get_watcher_lock_tag(), + LOCK_SHARED); + + C_SaferCond ctx; + Locker locker; + MockGetLockerRequest *req = MockGetLockerRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, true, &locker, &ctx); + req->send(); + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockManagedLockGetLockerRequest, GetLockInfoIncompatibleExclusive) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_lock_info(mock_image_ctx, 0, entity_name_t::CLIENT(1), "1.2.3.4", + "auto 123", util::get_watcher_lock_tag(), + LOCK_EXCLUSIVE); + + C_SaferCond ctx; + Locker locker; + MockGetLockerRequest *req = MockGetLockerRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, false, &locker, &ctx); + req->send(); + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockManagedLockGetLockerRequest, GetLockInfoExternalCookie) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_lock_info(mock_image_ctx, 0, entity_name_t::CLIENT(1), "1.2.3.4", + "external cookie", util::get_watcher_lock_tag(), + LOCK_EXCLUSIVE); + + C_SaferCond ctx; + Locker locker; + MockGetLockerRequest *req = MockGetLockerRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, true, &locker, &ctx); + req->send(); + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +} // namespace managed_lock +} // namespace librbd diff --git a/src/test/librbd/managed_lock/test_mock_ReacquireRequest.cc b/src/test/librbd/managed_lock/test_mock_ReacquireRequest.cc new file mode 100644 index 00000000..993be5aa --- /dev/null +++ b/src/test/librbd/managed_lock/test_mock_ReacquireRequest.cc @@ -0,0 +1,123 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "cls/lock/cls_lock_ops.h" +#include "librbd/managed_lock/ReacquireRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <list> + +// template definitions +#include "librbd/managed_lock/ReacquireRequest.cc" +template class librbd::managed_lock::ReacquireRequest<librbd::MockImageCtx>; + +namespace { + +MATCHER_P(IsLockType, exclusive, "") { + cls_lock_set_cookie_op op; + bufferlist bl; + bl.share(arg); + auto iter = bl.cbegin(); + decode(op, iter); + return op.type == (exclusive ? LOCK_EXCLUSIVE : LOCK_SHARED); +} + +} // anonymous namespace + +namespace librbd { +namespace managed_lock { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; + + +class TestMockManagedLockReacquireRequest : public TestMockFixture { +public: + typedef ReacquireRequest<MockImageCtx> MockReacquireRequest; + + void expect_set_cookie(MockImageCtx &mock_image_ctx, int r, + bool exclusive = true) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("lock"), + StrEq("set_cookie"), IsLockType(exclusive), _, _)) + .WillOnce(Return(r)); + } +}; + +TEST_F(TestMockManagedLockReacquireRequest, SuccessExclusive) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + InSequence seq; + expect_set_cookie(mock_image_ctx, 0); + + C_SaferCond ctx; + MockReacquireRequest *req = MockReacquireRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, "old_cookie", + "new_cookie", true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockManagedLockReacquireRequest, SuccessShared) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + InSequence seq; + expect_set_cookie(mock_image_ctx, 0, false); + + C_SaferCond ctx; + MockReacquireRequest *req = MockReacquireRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, "old_cookie", + "new_cookie", false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockManagedLockReacquireRequest, NotSupported) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + InSequence seq; + expect_set_cookie(mock_image_ctx, -EOPNOTSUPP); + + C_SaferCond ctx; + MockReacquireRequest *req = MockReacquireRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, "old_cookie", + "new_cookie", true, &ctx); + req->send(); + ASSERT_EQ(-EOPNOTSUPP, ctx.wait()); +} + +TEST_F(TestMockManagedLockReacquireRequest, Error) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + InSequence seq; + expect_set_cookie(mock_image_ctx, -EBUSY); + + C_SaferCond ctx; + MockReacquireRequest *req = MockReacquireRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.header_oid, "old_cookie", + "new_cookie", true, &ctx); + req->send(); + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +} // namespace managed_lock +} // namespace librbd diff --git a/src/test/librbd/managed_lock/test_mock_ReleaseRequest.cc b/src/test/librbd/managed_lock/test_mock_ReleaseRequest.cc new file mode 100644 index 00000000..af75f8f2 --- /dev/null +++ b/src/test/librbd/managed_lock/test_mock_ReleaseRequest.cc @@ -0,0 +1,91 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "librbd/managed_lock/ReleaseRequest.h" +#include "common/WorkQueue.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <list> + +namespace librbd { +namespace watcher { +template <> +struct Traits<MockImageCtx> { + typedef librbd::MockImageWatcher Watcher; +}; +} +} + +// template definitions +#include "librbd/managed_lock/ReleaseRequest.cc" +template class librbd::managed_lock::ReleaseRequest<librbd::MockImageCtx>; + +namespace librbd { +namespace managed_lock { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; + +static const std::string TEST_COOKIE("auto 123"); + +class TestMockManagedLockReleaseRequest : public TestMockFixture { +public: + typedef ReleaseRequest<MockImageCtx> MockReleaseRequest; + + void expect_unlock(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("lock"), + StrEq("unlock"), _, _, _)) + .WillOnce(Return(r)); + } + +}; + +TEST_F(TestMockManagedLockReleaseRequest, Success) { + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + expect_unlock(mock_image_ctx, 0); + + C_SaferCond ctx; + MockReleaseRequest *req = MockReleaseRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.image_watcher, ictx->op_work_queue, + mock_image_ctx.header_oid, TEST_COOKIE, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockManagedLockReleaseRequest, UnlockError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + expect_unlock(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + MockReleaseRequest *req = MockReleaseRequest::create( + mock_image_ctx.md_ctx, mock_image_ctx.image_watcher, ictx->op_work_queue, + mock_image_ctx.header_oid, TEST_COOKIE, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + +} + +} // namespace managed_lock +} // namespace librbd diff --git a/src/test/librbd/mirror/test_mock_DisableRequest.cc b/src/test/librbd/mirror/test_mock_DisableRequest.cc new file mode 100644 index 00000000..5af4e1c5 --- /dev/null +++ b/src/test/librbd/mirror/test_mock_DisableRequest.cc @@ -0,0 +1,585 @@ +// -*- 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/MockOperations.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "common/Mutex.h" +#include "librbd/MirroringWatcher.h" +#include "librbd/journal/PromoteRequest.h" +#include "librbd/mirror/DisableRequest.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +template <> +struct Journal<librbd::MockTestImageCtx> { + static Journal *s_instance; + static void is_tag_owner(librbd::MockTestImageCtx *, bool *is_primary, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->is_tag_owner(is_primary, on_finish); + } + + Journal() { + s_instance = this; + } + + MOCK_METHOD2(is_tag_owner, void(bool*, Context*)); +}; + +Journal<librbd::MockTestImageCtx> *Journal<librbd::MockTestImageCtx>::s_instance = nullptr; + +template <> +struct MirroringWatcher<librbd::MockTestImageCtx> { + static MirroringWatcher *s_instance; + static void notify_image_updated(librados::IoCtx &io_ctx, + cls::rbd::MirrorImageState mirror_image_state, + const std::string &image_id, + const std::string &global_image_id, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->notify_image_updated(mirror_image_state, image_id, + global_image_id, on_finish); + } + + MirroringWatcher() { + s_instance = this; + } + + MOCK_METHOD4(notify_image_updated, void(cls::rbd::MirrorImageState, + const std::string &, + const std::string &, + Context *)); +}; + +MirroringWatcher<librbd::MockTestImageCtx> *MirroringWatcher<librbd::MockTestImageCtx>::s_instance = nullptr; + +namespace journal { + +template <> +struct PromoteRequest<librbd::MockTestImageCtx> { + Context *on_finish = nullptr; + static PromoteRequest *s_instance; + static PromoteRequest *create(librbd::MockTestImageCtx *, bool force, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + PromoteRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +PromoteRequest<librbd::MockTestImageCtx> *PromoteRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace journal + +} // namespace librbd + +// template definitions +#include "librbd/mirror/DisableRequest.cc" +template class librbd::mirror::DisableRequest<librbd::MockTestImageCtx>; + +namespace librbd { +namespace mirror { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockMirrorDisableRequest : public TestMockFixture { +public: + typedef DisableRequest<MockTestImageCtx> MockDisableRequest; + typedef Journal<MockTestImageCtx> MockJournal; + typedef MirroringWatcher<MockTestImageCtx> MockMirroringWatcher; + typedef journal::PromoteRequest<MockTestImageCtx> MockPromoteRequest; + + void expect_get_mirror_image(MockTestImageCtx &mock_image_ctx, + const cls::rbd::MirrorImage &mirror_image, + int r) { + using ceph::encode; + bufferlist bl; + encode(mirror_image, bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_image_get"), + _, _, _)) + .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(bl)), + Return(r))); + } + + void expect_is_tag_owner(MockTestImageCtx &mock_image_ctx, + MockJournal &mock_journal, + bool is_primary, int r) { + EXPECT_CALL(mock_journal, is_tag_owner(_, _)) + .WillOnce(DoAll(SetArgPointee<0>(is_primary), + WithArg<1>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)))); + } + + void expect_set_mirror_image(MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_image_set"), + _, _, _)) + .WillOnce(Return(r)); + } + + void expect_remove_mirror_image(MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_image_remove"), + _, _, _)) + .WillOnce(Return(r)); + } + + void expect_notify_image_updated(MockTestImageCtx &mock_image_ctx, + MockMirroringWatcher &mock_mirroring_watcher, + cls::rbd::MirrorImageState state, + const std::string &global_id, int r) { + EXPECT_CALL(mock_mirroring_watcher, + notify_image_updated(state, mock_image_ctx.id, global_id, _)) + .WillOnce(WithArg<3>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } + + void expect_journal_client_list(MockTestImageCtx &mock_image_ctx, + const std::set<cls::journal::Client> &clients, + int r) { + bufferlist bl; + using ceph::encode; + encode(clients, bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(::journal::Journaler::header_oid(mock_image_ctx.id), + _, StrEq("journal"), StrEq("client_list"), _, _, _)) + .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(bl)), + Return(r))); + } + + void expect_journal_client_unregister(MockTestImageCtx &mock_image_ctx, + const std::string &client_id, + int r) { + bufferlist bl; + using ceph::encode; + encode(client_id, bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(::journal::Journaler::header_oid(mock_image_ctx.id), + _, StrEq("journal"), StrEq("client_unregister"), + ContentsEqual(bl), _, _)) + .WillOnce(Return(r)); + } + + void expect_journal_promote(MockTestImageCtx &mock_image_ctx, + MockPromoteRequest &mock_promote_request, int r) { + EXPECT_CALL(mock_promote_request, send()) + .WillOnce(FinishRequest(&mock_promote_request, r, &mock_image_ctx)); + } + + void expect_snap_remove(MockTestImageCtx &mock_image_ctx, + const std::string &snap_name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, snap_remove(_, StrEq(snap_name), _)) + .WillOnce(WithArg<2>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } + + template <typename T> + bufferlist encode(const T &t) { + using ceph::encode; + bufferlist bl; + encode(t, bl); + return bl; + } + +}; + +TEST_F(TestMockMirrorDisableRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + MockMirroringWatcher mock_mirroring_watcher; + + expect_op_work_queue(mock_image_ctx); + expect_snap_remove(mock_image_ctx, "snap 1", 0); + expect_snap_remove(mock_image_ctx, "snap 2", 0); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, + {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, + 0); + expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0); + expect_set_mirror_image(mock_image_ctx, 0); + expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher, + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, + "global id", -ESHUTDOWN); + expect_journal_client_list( + mock_image_ctx, { + {"", encode(journal::ClientData{journal::ImageClientMeta{}})}, + {"peer 1", encode(journal::ClientData{journal::MirrorPeerClientMeta{}})}, + {"peer 2", encode(journal::ClientData{journal::MirrorPeerClientMeta{ + "remote image id", {{cls::rbd::UserSnapshotNamespace(), "snap 1", boost::optional<uint64_t>(0)}, + {cls::rbd::UserSnapshotNamespace(), "snap 2", boost::optional<uint64_t>(0)}}} + })} + }, 0); + expect_journal_client_unregister(mock_image_ctx, "peer 1", 0); + expect_journal_client_unregister(mock_image_ctx, "peer 2", 0); + expect_journal_client_list(mock_image_ctx, {}, 0); + expect_remove_mirror_image(mock_image_ctx, 0); + expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher, + cls::rbd::MIRROR_IMAGE_STATE_DISABLED, + "global id", -ETIMEDOUT); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, SuccessNoRemove) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + MockMirroringWatcher mock_mirroring_watcher; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, + {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, + 0); + expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0); + expect_set_mirror_image(mock_image_ctx, 0); + expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher, + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, + "global id", 0); + expect_journal_client_list(mock_image_ctx, {}, 0); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, SuccessNonPrimary) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + MockMirroringWatcher mock_mirroring_watcher; + MockPromoteRequest mock_promote_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, + {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, + 0); + expect_is_tag_owner(mock_image_ctx, mock_journal, false, 0); + expect_set_mirror_image(mock_image_ctx, 0); + expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher, + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, + "global id", 0); + expect_journal_promote(mock_image_ctx, mock_promote_request, 0); + expect_journal_client_list(mock_image_ctx, {}, 0); + expect_remove_mirror_image(mock_image_ctx, 0); + expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher, + cls::rbd::MIRROR_IMAGE_STATE_DISABLED, + "global id", 0); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, true, true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, NonPrimaryError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + MockMirroringWatcher mock_mirroring_watcher; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, + {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, + 0); + expect_is_tag_owner(mock_image_ctx, mock_journal, false, 0); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, MirrorImageGetError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, {}, -EBADMSG); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-EBADMSG, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, IsTagOwnerError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, + {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, + 0); + expect_is_tag_owner(mock_image_ctx, mock_journal, true, -EBADMSG); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-EBADMSG, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, MirrorImageSetError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, + {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, + 0); + expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0); + expect_set_mirror_image(mock_image_ctx, -ENOENT); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, JournalPromoteError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + MockMirroringWatcher mock_mirroring_watcher; + MockPromoteRequest mock_promote_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, + {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, + 0); + expect_is_tag_owner(mock_image_ctx, mock_journal, false, 0); + expect_set_mirror_image(mock_image_ctx, 0); + expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher, + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, + "global id", 0); + expect_journal_promote(mock_image_ctx, mock_promote_request, -EPERM); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, true, true, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, JournalClientListError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + MockMirroringWatcher mock_mirroring_watcher; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, + {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, + 0); + expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0); + expect_set_mirror_image(mock_image_ctx, 0); + expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher, + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, + "global id", 0); + expect_journal_client_list(mock_image_ctx, {}, -EBADMSG); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-EBADMSG, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, SnapRemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + MockMirroringWatcher mock_mirroring_watcher; + + expect_op_work_queue(mock_image_ctx); + expect_snap_remove(mock_image_ctx, "snap 1", 0); + expect_snap_remove(mock_image_ctx, "snap 2", -EPERM); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, + {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, + 0); + expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0); + expect_set_mirror_image(mock_image_ctx, 0); + expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher, + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, + "global id", 0); + expect_journal_client_list( + mock_image_ctx, { + {"", encode(journal::ClientData{journal::ImageClientMeta{}})}, + {"peer 1", encode(journal::ClientData{journal::MirrorPeerClientMeta{}})}, + {"peer 2", encode(journal::ClientData{journal::MirrorPeerClientMeta{ + "remote image id", {{cls::rbd::UserSnapshotNamespace(), "snap 1", boost::optional<uint64_t>(0)}, + {cls::rbd::UserSnapshotNamespace(), "snap 2", boost::optional<uint64_t>(0)}}} + })} + }, 0); + expect_journal_client_unregister(mock_image_ctx, "peer 1", 0); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, JournalClientUnregisterError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + MockMirroringWatcher mock_mirroring_watcher; + + expect_op_work_queue(mock_image_ctx); + expect_snap_remove(mock_image_ctx, "snap 1", 0); + expect_snap_remove(mock_image_ctx, "snap 2", 0); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, + {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, + 0); + expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0); + expect_set_mirror_image(mock_image_ctx, 0); + expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher, + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, + "global id", 0); + expect_journal_client_list( + mock_image_ctx, { + {"", encode(journal::ClientData{journal::ImageClientMeta{}})}, + {"peer 1", encode(journal::ClientData{journal::MirrorPeerClientMeta{}})}, + {"peer 2", encode(journal::ClientData{journal::MirrorPeerClientMeta{ + "remote image id", {{cls::rbd::UserSnapshotNamespace(), "snap 1", boost::optional<uint64_t>(0)}, + {cls::rbd::UserSnapshotNamespace(), "snap 2", boost::optional<uint64_t>(0)}}} + })} + }, 0); + expect_journal_client_unregister(mock_image_ctx, "peer 1", -EINVAL); + expect_journal_client_unregister(mock_image_ctx, "peer 2", 0); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, MirrorImageRemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + MockMirroringWatcher mock_mirroring_watcher; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_mirror_image(mock_image_ctx, + {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, + 0); + expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0); + expect_set_mirror_image(mock_image_ctx, 0); + expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher, + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, + "global id", 0); + expect_journal_client_list(mock_image_ctx, {}, 0); + expect_remove_mirror_image(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace mirror +} // namespace librbd + diff --git a/src/test/librbd/mock/MockContextWQ.h b/src/test/librbd/mock/MockContextWQ.h new file mode 100644 index 00000000..f900b627 --- /dev/null +++ b/src/test/librbd/mock/MockContextWQ.h @@ -0,0 +1,19 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_CONTEXT_WQ_H +#define CEPH_TEST_LIBRBD_MOCK_CONTEXT_WQ_H + +#include "gmock/gmock.h" + +struct Context; + +namespace librbd { + +struct MockContextWQ { + MOCK_METHOD2(queue, void(Context *, int r)); +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_CONTEXT_WQ_H diff --git a/src/test/librbd/mock/MockExclusiveLock.h b/src/test/librbd/mock/MockExclusiveLock.h new file mode 100644 index 00000000..efb4fa4e --- /dev/null +++ b/src/test/librbd/mock/MockExclusiveLock.h @@ -0,0 +1,41 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_EXCLUSIVE_LOCK_H +#define CEPH_TEST_LIBRBD_MOCK_EXCLUSIVE_LOCK_H + +#include "include/int_types.h" +#include "include/rados/librados.hpp" +#include "librbd/exclusive_lock/Policy.h" +#include "gmock/gmock.h" + +class Context; + +namespace librbd { + +struct MockExclusiveLock { + MOCK_CONST_METHOD0(is_lock_owner, bool()); + + MOCK_METHOD2(init, void(uint64_t features, Context*)); + MOCK_METHOD1(shut_down, void(Context*)); + + MOCK_METHOD1(reacquire_lock, void(Context*)); + MOCK_METHOD1(try_acquire_lock, void(Context*)); + + MOCK_METHOD1(block_requests, void(int)); + MOCK_METHOD0(unblock_requests, void()); + + MOCK_METHOD1(acquire_lock, void(Context *)); + MOCK_METHOD1(release_lock, void(Context *)); + + MOCK_METHOD2(accept_request, bool(exclusive_lock::OperationRequestType, + int *)); + MOCK_METHOD0(accept_ops, bool()); + MOCK_METHOD0(get_unlocked_op_error, int()); + + MOCK_METHOD1(start_op, Context*(int*)); +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_EXCLUSIVE_LOCK_H diff --git a/src/test/librbd/mock/MockImageCtx.cc b/src/test/librbd/mock/MockImageCtx.cc new file mode 100644 index 00000000..f4b2430f --- /dev/null +++ b/src/test/librbd/mock/MockImageCtx.cc @@ -0,0 +1,10 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/mock/MockImageCtx.h" + +namespace librbd { + +MockImageCtx* MockImageCtx::s_instance = nullptr; + +} // namespace librbd diff --git a/src/test/librbd/mock/MockImageCtx.h b/src/test/librbd/mock/MockImageCtx.h new file mode 100644 index 00000000..01291f04 --- /dev/null +++ b/src/test/librbd/mock/MockImageCtx.h @@ -0,0 +1,320 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_IMAGE_CTX_H +#define CEPH_TEST_LIBRBD_MOCK_IMAGE_CTX_H + +#include "include/rados/librados.hpp" +#include "test/librbd/mock/MockContextWQ.h" +#include "test/librbd/mock/MockExclusiveLock.h" +#include "test/librbd/mock/MockImageState.h" +#include "test/librbd/mock/MockImageWatcher.h" +#include "test/librbd/mock/MockJournal.h" +#include "test/librbd/mock/MockObjectMap.h" +#include "test/librbd/mock/MockOperations.h" +#include "test/librbd/mock/MockReadahead.h" +#include "test/librbd/mock/io/MockImageRequestWQ.h" +#include "test/librbd/mock/io/MockObjectDispatcher.h" +#include "common/RWLock.h" +#include "common/WorkQueue.h" +#include "common/zipkin_trace.h" +#include "librbd/ImageCtx.h" +#include "gmock/gmock.h" +#include <string> + +namespace librbd { + +namespace cache { class MockImageCache; } +namespace operation { +template <typename> class ResizeRequest; +} + +struct MockImageCtx { + static MockImageCtx *s_instance; + static MockImageCtx *create(const std::string &image_name, + const std::string &image_id, + const char *snap, librados::IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + MOCK_METHOD0(destroy, void()); + + MockImageCtx(librbd::ImageCtx &image_ctx) + : image_ctx(&image_ctx), + cct(image_ctx.cct), + perfcounter(image_ctx.perfcounter), + snap_namespace(image_ctx.snap_namespace), + snap_name(image_ctx.snap_name), + snap_id(image_ctx.snap_id), + snap_exists(image_ctx.snap_exists), + snapc(image_ctx.snapc), + snaps(image_ctx.snaps), + snap_info(image_ctx.snap_info), + snap_ids(image_ctx.snap_ids), + old_format(image_ctx.old_format), + read_only(image_ctx.read_only), + clone_copy_on_read(image_ctx.clone_copy_on_read), + lockers(image_ctx.lockers), + exclusive_locked(image_ctx.exclusive_locked), + lock_tag(image_ctx.lock_tag), + owner_lock(image_ctx.owner_lock), + md_lock(image_ctx.md_lock), + snap_lock(image_ctx.snap_lock), + timestamp_lock(image_ctx.timestamp_lock), + parent_lock(image_ctx.parent_lock), + object_map_lock(image_ctx.object_map_lock), + async_ops_lock(image_ctx.async_ops_lock), + copyup_list_lock(image_ctx.copyup_list_lock), + order(image_ctx.order), + size(image_ctx.size), + features(image_ctx.features), + flags(image_ctx.flags), + op_features(image_ctx.op_features), + operations_disabled(image_ctx.operations_disabled), + stripe_unit(image_ctx.stripe_unit), + stripe_count(image_ctx.stripe_count), + object_prefix(image_ctx.object_prefix), + header_oid(image_ctx.header_oid), + id(image_ctx.id), + name(image_ctx.name), + parent_md(image_ctx.parent_md), + format_string(image_ctx.format_string), + group_spec(image_ctx.group_spec), + layout(image_ctx.layout), + io_work_queue(new io::MockImageRequestWQ()), + io_object_dispatcher(new io::MockObjectDispatcher()), + op_work_queue(new MockContextWQ()), + readahead_max_bytes(image_ctx.readahead_max_bytes), + event_socket(image_ctx.event_socket), + parent(NULL), operations(new MockOperations()), + state(new MockImageState()), + image_watcher(NULL), object_map(NULL), + exclusive_lock(NULL), journal(NULL), + trace_endpoint(image_ctx.trace_endpoint), + sparse_read_threshold_bytes(image_ctx.sparse_read_threshold_bytes), + discard_granularity_bytes(image_ctx.discard_granularity_bytes), + mirroring_replay_delay(image_ctx.mirroring_replay_delay), + non_blocking_aio(image_ctx.non_blocking_aio), + blkin_trace_all(image_ctx.blkin_trace_all), + enable_alloc_hint(image_ctx.enable_alloc_hint), + alloc_hint_flags(image_ctx.alloc_hint_flags), + ignore_migrating(image_ctx.ignore_migrating), + mtime_update_interval(image_ctx.mtime_update_interval), + atime_update_interval(image_ctx.atime_update_interval), + cache(image_ctx.cache), + config(image_ctx.config) + { + md_ctx.dup(image_ctx.md_ctx); + data_ctx.dup(image_ctx.data_ctx); + + if (image_ctx.image_watcher != NULL) { + image_watcher = new MockImageWatcher(); + } + } + + ~MockImageCtx() { + wait_for_async_requests(); + image_ctx->md_ctx.aio_flush(); + image_ctx->data_ctx.aio_flush(); + image_ctx->op_work_queue->drain(); + delete state; + delete operations; + delete image_watcher; + delete op_work_queue; + delete io_work_queue; + delete io_object_dispatcher; + } + + void wait_for_async_requests() { + async_ops_lock.Lock(); + if (async_requests.empty()) { + async_ops_lock.Unlock(); + return; + } + + C_SaferCond ctx; + async_requests_waiters.push_back(&ctx); + async_ops_lock.Unlock(); + + ctx.wait(); + } + + MOCK_METHOD1(init_layout, void(int64_t)); + + MOCK_CONST_METHOD1(get_object_name, std::string(uint64_t)); + MOCK_CONST_METHOD0(get_object_size, uint64_t()); + MOCK_CONST_METHOD0(get_current_size, uint64_t()); + MOCK_CONST_METHOD1(get_image_size, uint64_t(librados::snap_t)); + MOCK_CONST_METHOD1(get_object_count, uint64_t(librados::snap_t)); + MOCK_CONST_METHOD1(get_read_flags, int(librados::snap_t)); + MOCK_CONST_METHOD2(get_flags, int(librados::snap_t in_snap_id, + uint64_t *flags)); + MOCK_CONST_METHOD2(get_snap_id, + librados::snap_t(cls::rbd::SnapshotNamespace snap_namespace, + std::string in_snap_name)); + MOCK_CONST_METHOD1(get_snap_info, const SnapInfo*(librados::snap_t)); + MOCK_CONST_METHOD2(get_snap_name, int(librados::snap_t, std::string *)); + MOCK_CONST_METHOD2(get_snap_namespace, int(librados::snap_t, + cls::rbd::SnapshotNamespace *out_snap_namespace)); + MOCK_CONST_METHOD2(get_parent_spec, int(librados::snap_t in_snap_id, + cls::rbd::ParentImageSpec *pspec)); + MOCK_CONST_METHOD1(get_parent_info, const ParentImageInfo*(librados::snap_t)); + MOCK_CONST_METHOD2(get_parent_overlap, int(librados::snap_t in_snap_id, + uint64_t *overlap)); + MOCK_CONST_METHOD2(prune_parent_extents, uint64_t(vector<pair<uint64_t,uint64_t> >& , + uint64_t)); + + MOCK_CONST_METHOD2(is_snap_protected, int(librados::snap_t in_snap_id, + bool *is_protected)); + MOCK_CONST_METHOD2(is_snap_unprotected, int(librados::snap_t in_snap_id, + bool *is_unprotected)); + + MOCK_CONST_METHOD0(get_create_timestamp, utime_t()); + MOCK_CONST_METHOD0(get_access_timestamp, utime_t()); + MOCK_CONST_METHOD0(get_modify_timestamp, utime_t()); + + MOCK_METHOD1(set_access_timestamp, void(const utime_t at)); + MOCK_METHOD1(set_modify_timestamp, void(const utime_t at)); + + MOCK_METHOD8(add_snap, void(cls::rbd::SnapshotNamespace in_snap_namespace, + std::string in_snap_name, + librados::snap_t id, + uint64_t in_size, const ParentImageInfo &parent, + uint8_t protection_status, uint64_t flags, utime_t timestamp)); + MOCK_METHOD3(rm_snap, void(cls::rbd::SnapshotNamespace in_snap_namespace, + std::string in_snap_name, + librados::snap_t id)); + + MOCK_METHOD0(user_flushed, void()); + MOCK_METHOD1(flush_copyup, void(Context *)); + + MOCK_CONST_METHOD1(test_features, bool(uint64_t test_features)); + MOCK_CONST_METHOD2(test_features, bool(uint64_t test_features, + const RWLock &in_snap_lock)); + + MOCK_CONST_METHOD1(test_op_features, bool(uint64_t op_features)); + + MOCK_METHOD1(cancel_async_requests, void(Context*)); + + MOCK_METHOD0(create_exclusive_lock, MockExclusiveLock*()); + MOCK_METHOD1(create_object_map, MockObjectMap*(uint64_t)); + MOCK_METHOD0(create_journal, MockJournal*()); + + MOCK_METHOD0(notify_update, void()); + MOCK_METHOD1(notify_update, void(Context *)); + + MOCK_CONST_METHOD0(get_exclusive_lock_policy, exclusive_lock::Policy*()); + MOCK_CONST_METHOD0(get_journal_policy, journal::Policy*()); + MOCK_METHOD1(set_journal_policy, void(journal::Policy*)); + + MOCK_METHOD2(apply_metadata, int(const std::map<std::string, bufferlist> &, + bool)); + + MOCK_CONST_METHOD0(get_stripe_count, uint64_t()); + MOCK_CONST_METHOD0(get_stripe_period, uint64_t()); + + MOCK_CONST_METHOD0(is_writeback_cache_enabled, bool()); + + ImageCtx *image_ctx; + CephContext *cct; + PerfCounters *perfcounter; + + cls::rbd::SnapshotNamespace snap_namespace; + std::string snap_name; + uint64_t snap_id; + bool snap_exists; + + ::SnapContext snapc; + std::vector<librados::snap_t> snaps; + std::map<librados::snap_t, SnapInfo> snap_info; + std::map<std::pair<cls::rbd::SnapshotNamespace, std::string>, librados::snap_t> snap_ids; + + bool old_format; + bool read_only; + + bool clone_copy_on_read; + + std::map<rados::cls::lock::locker_id_t, + rados::cls::lock::locker_info_t> lockers; + bool exclusive_locked; + std::string lock_tag; + + librados::IoCtx md_ctx; + librados::IoCtx data_ctx; + + RWLock &owner_lock; + RWLock &md_lock; + RWLock &snap_lock; + RWLock ×tamp_lock; + RWLock &parent_lock; + RWLock &object_map_lock; + Mutex &async_ops_lock; + Mutex ©up_list_lock; + + uint8_t order; + uint64_t size; + uint64_t features; + uint64_t flags; + uint64_t op_features; + bool operations_disabled; + uint64_t stripe_unit; + uint64_t stripe_count; + std::string object_prefix; + std::string header_oid; + std::string id; + std::string name; + ParentImageInfo parent_md; + MigrationInfo migration_info; + char *format_string; + cls::rbd::GroupSpec group_spec; + + file_layout_t layout; + + xlist<operation::ResizeRequest<MockImageCtx>*> resize_reqs; + xlist<AsyncRequest<MockImageCtx>*> async_requests; + std::list<Context*> async_requests_waiters; + + std::map<uint64_t, io::CopyupRequest<MockImageCtx>*> copyup_list; + + io::MockImageRequestWQ *io_work_queue; + io::MockObjectDispatcher *io_object_dispatcher; + MockContextWQ *op_work_queue; + + cache::MockImageCache *image_cache = nullptr; + + MockReadahead readahead; + uint64_t readahead_max_bytes; + + EventSocket &event_socket; + + MockImageCtx *parent; + MockOperations *operations; + MockImageState *state; + + MockImageWatcher *image_watcher; + MockObjectMap *object_map; + MockExclusiveLock *exclusive_lock; + MockJournal *journal; + + ZTracer::Endpoint trace_endpoint; + + uint64_t sparse_read_threshold_bytes; + uint32_t discard_granularity_bytes; + int mirroring_replay_delay; + bool non_blocking_aio; + bool blkin_trace_all; + bool enable_alloc_hint; + uint32_t alloc_hint_flags; + bool ignore_migrating; + uint64_t mtime_update_interval; + uint64_t atime_update_interval; + bool cache; + + ConfigProxy config; + std::set<std::string> config_overrides; +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_IMAGE_CTX_H diff --git a/src/test/librbd/mock/MockImageState.h b/src/test/librbd/mock/MockImageState.h new file mode 100644 index 00000000..8e399b6c --- /dev/null +++ b/src/test/librbd/mock/MockImageState.h @@ -0,0 +1,34 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_IMAGE_STATE_H +#define CEPH_TEST_LIBRBD_MOCK_IMAGE_STATE_H + +#include <gmock/gmock.h> + +#include "cls/rbd/cls_rbd_types.h" + +class Context; + +namespace librbd { + +struct MockImageState { + MOCK_CONST_METHOD0(is_refresh_required, bool()); + MOCK_METHOD1(refresh, void(Context*)); + + MOCK_METHOD2(open, void(bool, Context*)); + + MOCK_METHOD0(close, int()); + MOCK_METHOD1(close, void(Context*)); + + MOCK_METHOD2(snap_set, void(uint64_t snap_id, Context*)); + + MOCK_METHOD1(prepare_lock, void(Context*)); + MOCK_METHOD0(handle_prepare_lock_complete, void()); + + MOCK_METHOD0(handle_update_notification, void()); +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_IMAGE_STATE_H diff --git a/src/test/librbd/mock/MockImageWatcher.h b/src/test/librbd/mock/MockImageWatcher.h new file mode 100644 index 00000000..c7e94279 --- /dev/null +++ b/src/test/librbd/mock/MockImageWatcher.h @@ -0,0 +1,29 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_IMAGE_WATCHER_H +#define CEPH_TEST_LIBRBD_MOCK_IMAGE_WATCHER_H + +#include "gmock/gmock.h" + +class Context; + +namespace librbd { + +struct MockImageWatcher { + MOCK_METHOD0(is_registered, bool()); + MOCK_METHOD0(is_unregistered, bool()); + MOCK_METHOD0(is_blacklisted, bool()); + MOCK_METHOD0(unregister_watch, void()); + MOCK_METHOD1(flush, void(Context *)); + + MOCK_CONST_METHOD0(get_watch_handle, uint64_t()); + + MOCK_METHOD0(notify_acquired_lock, void()); + MOCK_METHOD0(notify_released_lock, void()); + MOCK_METHOD0(notify_request_lock, void()); +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_IMAGE_WATCHER_H diff --git a/src/test/librbd/mock/MockJournal.cc b/src/test/librbd/mock/MockJournal.cc new file mode 100644 index 00000000..97feb018 --- /dev/null +++ b/src/test/librbd/mock/MockJournal.cc @@ -0,0 +1,10 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/mock/MockJournal.h" + +namespace librbd { + +MockJournal *MockJournal::s_instance = nullptr; + +} // namespace librbd diff --git a/src/test/librbd/mock/MockJournal.h b/src/test/librbd/mock/MockJournal.h new file mode 100644 index 00000000..31806217 --- /dev/null +++ b/src/test/librbd/mock/MockJournal.h @@ -0,0 +1,92 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_JOURNAL_H +#define CEPH_TEST_LIBRBD_MOCK_JOURNAL_H + +#include "gmock/gmock.h" +#include "include/rados/librados_fwd.hpp" +#include "librbd/Journal.h" +#include "librbd/journal/Types.h" +#include <list> + +struct Context; +struct ContextWQ; + +namespace librbd { + +struct ImageCtx; + +struct MockJournal { + static MockJournal *s_instance; + static MockJournal *get_instance() { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + template <typename ImageCtxT> + static int is_tag_owner(ImageCtxT *image_ctx, bool *is_tag_owner) { + return get_instance()->is_tag_owner(is_tag_owner); + } + + static void get_tag_owner(librados::IoCtx &, + const std::string &global_image_id, + std::string *tag_owner, ContextWQ *work_queue, + Context *on_finish) { + get_instance()->get_tag_owner(global_image_id, tag_owner, + work_queue, on_finish); + } + + MockJournal() { + s_instance = this; + } + + MOCK_CONST_METHOD0(is_journal_ready, bool()); + MOCK_CONST_METHOD0(is_journal_replaying, bool()); + MOCK_CONST_METHOD0(is_journal_appending, bool()); + + MOCK_METHOD1(wait_for_journal_ready, void(Context *)); + + MOCK_METHOD4(get_tag_owner, void(const std::string &, + std::string *, ContextWQ *, + Context *)); + + MOCK_CONST_METHOD0(is_tag_owner, bool()); + MOCK_CONST_METHOD1(is_tag_owner, int(bool *)); + MOCK_METHOD3(allocate_tag, void(const std::string &mirror_uuid, + const journal::TagPredecessor &predecessor, + Context *on_finish)); + + MOCK_METHOD1(open, void(Context *)); + MOCK_METHOD1(close, void(Context *)); + + MOCK_CONST_METHOD0(get_tag_tid, uint64_t()); + MOCK_CONST_METHOD0(get_tag_data, journal::TagData()); + + MOCK_METHOD0(allocate_op_tid, uint64_t()); + + MOCK_METHOD0(user_flushed, void()); + + MOCK_METHOD3(append_op_event_mock, void(uint64_t, const journal::EventEntry&, + Context *)); + void append_op_event(uint64_t op_tid, journal::EventEntry &&event_entry, + Context *on_safe) { + // googlemock doesn't support move semantics + append_op_event_mock(op_tid, event_entry, on_safe); + } + + MOCK_METHOD2(flush_event, void(uint64_t, Context *)); + MOCK_METHOD2(wait_event, void(uint64_t, Context *)); + + MOCK_METHOD3(commit_op_event, void(uint64_t, int, Context *)); + MOCK_METHOD2(replay_op_ready, void(uint64_t, Context *)); + + MOCK_METHOD1(add_listener, void(journal::Listener *)); + MOCK_METHOD1(remove_listener, void(journal::Listener *)); + + MOCK_METHOD1(is_resync_requested, int(bool *)); +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_JOURNAL_H diff --git a/src/test/librbd/mock/MockJournalPolicy.h b/src/test/librbd/mock/MockJournalPolicy.h new file mode 100644 index 00000000..33bb252a --- /dev/null +++ b/src/test/librbd/mock/MockJournalPolicy.h @@ -0,0 +1,22 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_JOURNAL_POLICY_H +#define CEPH_TEST_LIBRBD_MOCK_JOURNAL_POLICY_H + +#include "librbd/journal/Policy.h" +#include "gmock/gmock.h" + +namespace librbd { + +struct MockJournalPolicy : public journal::Policy { + + MOCK_CONST_METHOD0(append_disabled, bool()); + MOCK_CONST_METHOD0(journal_disabled, bool()); + MOCK_METHOD1(allocate_tag_on_lock, void(Context*)); + +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_JOURNAL_POLICY_H diff --git a/src/test/librbd/mock/MockObjectMap.h b/src/test/librbd/mock/MockObjectMap.h new file mode 100644 index 00000000..2692a30f --- /dev/null +++ b/src/test/librbd/mock/MockObjectMap.h @@ -0,0 +1,70 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_OBJECT_MAP_H +#define CEPH_TEST_LIBRBD_MOCK_OBJECT_MAP_H + +#include "common/RWLock.h" +#include "librbd/Utils.h" +#include "gmock/gmock.h" + +namespace librbd { + +struct MockObjectMap { + MOCK_METHOD1(at, uint8_t(uint64_t)); + uint8_t operator[](uint64_t object_no) { + return at(object_no); + } + + MOCK_CONST_METHOD1(enabled, bool(const RWLock &object_map_lock)); + + MOCK_CONST_METHOD0(size, uint64_t()); + + MOCK_METHOD1(open, void(Context *on_finish)); + MOCK_METHOD1(close, void(Context *on_finish)); + + MOCK_METHOD3(aio_resize, void(uint64_t new_size, uint8_t default_object_state, + Context *on_finish)); + + template <typename T, void(T::*MF)(int) = &T::complete> + bool aio_update(uint64_t snap_id, uint64_t start_object_no, uint8_t new_state, + const boost::optional<uint8_t> ¤t_state, + const ZTracer::Trace &parent_trace, bool ignore_enoent, + T *callback_object) { + return aio_update<T, MF>(snap_id, start_object_no, start_object_no + 1, + new_state, current_state, parent_trace, + ignore_enoent, callback_object); + } + + template <typename T, void(T::*MF)(int) = &T::complete> + bool aio_update(uint64_t snap_id, uint64_t start_object_no, + uint64_t end_object_no, uint8_t new_state, + const boost::optional<uint8_t> ¤t_state, + const ZTracer::Trace &parent_trace, bool ignore_enoent, + T *callback_object) { + auto ctx = util::create_context_callback<T, MF>(callback_object); + bool updated = aio_update(snap_id, start_object_no, end_object_no, + new_state, current_state, parent_trace, + ignore_enoent, ctx); + if (!updated) { + delete ctx; + } + return updated; + } + MOCK_METHOD8(aio_update, bool(uint64_t snap_id, uint64_t start_object_no, + uint64_t end_object_no, uint8_t new_state, + const boost::optional<uint8_t> ¤t_state, + const ZTracer::Trace &parent_trace, + bool ignore_enoent, Context *on_finish)); + + MOCK_METHOD2(snapshot_add, void(uint64_t snap_id, Context *on_finish)); + MOCK_METHOD2(snapshot_remove, void(uint64_t snap_id, Context *on_finish)); + MOCK_METHOD2(rollback, void(uint64_t snap_id, Context *on_finish)); + + MOCK_CONST_METHOD1(object_may_exist, bool(uint64_t)); + +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_OBJECT_MAP_H diff --git a/src/test/librbd/mock/MockOperations.h b/src/test/librbd/mock/MockOperations.h new file mode 100644 index 00000000..49876d19 --- /dev/null +++ b/src/test/librbd/mock/MockOperations.h @@ -0,0 +1,69 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_OPERATIONS_H +#define CEPH_TEST_LIBRBD_MOCK_OPERATIONS_H + +#include "cls/rbd/cls_rbd_types.h" +#include "include/int_types.h" +#include "include/rbd/librbd.hpp" +#include "gmock/gmock.h" +#include <string> + +class Context; + +namespace librbd { + +struct MockOperations { + MOCK_METHOD2(execute_flatten, void(ProgressContext &prog_ctx, + Context *on_finish)); + MOCK_METHOD2(execute_rebuild_object_map, void(ProgressContext &prog_ctx, + Context *on_finish)); + MOCK_METHOD2(execute_rename, void(const std::string &dstname, + Context *on_finish)); + MOCK_METHOD5(execute_resize, void(uint64_t size, bool allow_shrink, + ProgressContext &prog_ctx, + Context *on_finish, + uint64_t journal_op_tid)); + MOCK_METHOD3(snap_create, void(const cls::rbd::SnapshotNamespace &snapshot_namespace, + const std::string &snap_name, + Context *on_finish)); + MOCK_METHOD5(execute_snap_create, void(const cls::rbd::SnapshotNamespace &snapshot_namespace, + const std::string &snap_name, + Context *on_finish, + uint64_t journal_op_tid, + bool skip_object_map)); + MOCK_METHOD3(snap_remove, void(const cls::rbd::SnapshotNamespace &snap_namespace, + const std::string &snap_name, + Context *on_finish)); + MOCK_METHOD3(execute_snap_remove, void(const cls::rbd::SnapshotNamespace &snap_namespace, + const std::string &snap_name, + Context *on_finish)); + MOCK_METHOD3(execute_snap_rename, void(uint64_t src_snap_id, + const std::string &snap_name, + Context *on_finish)); + MOCK_METHOD4(execute_snap_rollback, void(const cls::rbd::SnapshotNamespace &snap_namespace, + const std::string &snap_name, + ProgressContext &prog_ctx, + Context *on_finish)); + MOCK_METHOD3(execute_snap_protect, void(const cls::rbd::SnapshotNamespace &snap_namespace, + const std::string &snap_name, + Context *on_finish)); + MOCK_METHOD3(execute_snap_unprotect, void(const cls::rbd::SnapshotNamespace &snap_namespace, + const std::string &snap_name, + Context *on_finish)); + MOCK_METHOD2(execute_snap_set_limit, void(uint64_t limit, + Context *on_finish)); + MOCK_METHOD4(execute_update_features, void(uint64_t features, bool enabled, + Context *on_finish, + uint64_t journal_op_tid)); + MOCK_METHOD3(execute_metadata_set, void(const std::string &key, + const std::string &value, + Context *on_finish)); + MOCK_METHOD2(execute_metadata_remove, void(const std::string &key, + Context *on_finish)); +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_OPERATIONS_H diff --git a/src/test/librbd/mock/MockReadahead.h b/src/test/librbd/mock/MockReadahead.h new file mode 100644 index 00000000..40fceaa7 --- /dev/null +++ b/src/test/librbd/mock/MockReadahead.h @@ -0,0 +1,21 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_READAHEAD_H +#define CEPH_TEST_LIBRBD_MOCK_READAHEAD_H + +#include "include/int_types.h" +#include "gmock/gmock.h" + +class Context; + +namespace librbd { + +struct MockReadahead { + MOCK_METHOD1(set_max_readahead_size, void(uint64_t)); + MOCK_METHOD1(wait_for_pending, void(Context *)); +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_READAHEAD_H diff --git a/src/test/librbd/mock/cache/MockImageCache.h b/src/test/librbd/mock/cache/MockImageCache.h new file mode 100644 index 00000000..dd16a90f --- /dev/null +++ b/src/test/librbd/mock/cache/MockImageCache.h @@ -0,0 +1,56 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_CACHE_MOCK_IMAGE_CACHE_H +#define CEPH_TEST_LIBRBD_CACHE_MOCK_IMAGE_CACHE_H + +#include "gmock/gmock.h" +#include <vector> + +namespace librbd { +namespace cache { + +struct MockImageCache { + typedef std::vector<std::pair<uint64_t,uint64_t> > Extents; + + MOCK_METHOD4(aio_read_mock, void(const Extents &, ceph::bufferlist*, int, + Context *)); + void aio_read(Extents&& image_extents, ceph::bufferlist* bl, + int fadvise_flags, Context *on_finish) { + aio_read_mock(image_extents, bl, fadvise_flags, on_finish); + } + + + MOCK_METHOD4(aio_write_mock, void(const Extents &, const ceph::bufferlist &, + int, Context *)); + void aio_write(Extents&& image_extents, ceph::bufferlist&& bl, + int fadvise_flags, Context *on_finish) { + aio_write_mock(image_extents, bl, fadvise_flags, on_finish); + } + + MOCK_METHOD4(aio_discard, void(uint64_t, uint64_t, uint32_t, Context *)); + MOCK_METHOD1(aio_flush, void(Context *)); + MOCK_METHOD5(aio_writesame_mock, void(uint64_t, uint64_t, ceph::bufferlist& bl, + int, Context *)); + void aio_writesame(uint64_t off, uint64_t len, ceph::bufferlist&& bl, + int fadvise_flags, Context *on_finish) { + aio_writesame_mock(off, len, bl, fadvise_flags, on_finish); + } + + MOCK_METHOD6(aio_compare_and_write_mock, void(const Extents &, + const ceph::bufferlist &, + const ceph::bufferlist &, + uint64_t *, int, Context *)); + + void aio_compare_and_write(Extents&& image_extents, ceph::bufferlist&& cmp_bl, + ceph::bufferlist&& bl, uint64_t *mismatch_offset, + int fadvise_flags, Context *on_finish) { + aio_compare_and_write_mock(image_extents, cmp_bl, bl, mismatch_offset, + fadvise_flags, on_finish); + } +}; + +} // namespace cache +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_CACHE_MOCK_IMAGE_CACHE_H diff --git a/src/test/librbd/mock/exclusive_lock/MockPolicy.h b/src/test/librbd/mock/exclusive_lock/MockPolicy.h new file mode 100644 index 00000000..f49eeb23 --- /dev/null +++ b/src/test/librbd/mock/exclusive_lock/MockPolicy.h @@ -0,0 +1,23 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_EXCLUSIVE_LOCK_POLICY_H +#define CEPH_TEST_LIBRBD_MOCK_EXCLUSIVE_LOCK_POLICY_H + +#include "librbd/exclusive_lock/Policy.h" +#include <gmock/gmock.h> + +namespace librbd { +namespace exclusive_lock { + +struct MockPolicy : public Policy { + + MOCK_METHOD0(may_auto_request_lock, bool()); + MOCK_METHOD1(lock_requested, int(bool)); + MOCK_METHOD1(accept_blocked_request, bool(OperationRequestType)); +}; + +} // namespace exclusive_lock +} // librbd + +#endif diff --git a/src/test/librbd/mock/io/MockImageRequestWQ.h b/src/test/librbd/mock/io/MockImageRequestWQ.h new file mode 100644 index 00000000..ab080452 --- /dev/null +++ b/src/test/librbd/mock/io/MockImageRequestWQ.h @@ -0,0 +1,25 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_IO_IMAGE_REQUEST_WQ_H +#define CEPH_TEST_LIBRBD_MOCK_IO_IMAGE_REQUEST_WQ_H + +#include "gmock/gmock.h" +#include "librbd/io/Types.h" + +class Context; + +namespace librbd { +namespace io { + +struct MockImageRequestWQ { + MOCK_METHOD1(block_writes, void(Context *)); + MOCK_METHOD0(unblock_writes, void()); + + MOCK_METHOD2(set_require_lock, void(Direction, bool)); +}; + +} // namespace io +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_IO_IMAGE_REQUEST_WQ_H diff --git a/src/test/librbd/mock/io/MockObjectDispatch.h b/src/test/librbd/mock/io/MockObjectDispatch.h new file mode 100644 index 00000000..5f308dab --- /dev/null +++ b/src/test/librbd/mock/io/MockObjectDispatch.h @@ -0,0 +1,121 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_IO_OBJECT_DISPATCH_H +#define CEPH_TEST_LIBRBD_MOCK_IO_OBJECT_DISPATCH_H + +#include "gmock/gmock.h" +#include "librbd/io/ObjectDispatchInterface.h" +#include "librbd/io/Types.h" + +class Context; + +namespace librbd { +namespace io { + +struct MockObjectDispatch : public ObjectDispatchInterface { +public: + RWLock lock; + + MockObjectDispatch() : lock("MockObjectDispatch::lock", true, false) { + } + + MOCK_CONST_METHOD0(get_object_dispatch_layer, ObjectDispatchLayer()); + + MOCK_METHOD1(shut_down, void(Context*)); + + MOCK_METHOD8(execute_read, + bool(uint64_t, uint64_t, uint64_t, librados::snap_t, + ceph::bufferlist*, ExtentMap*, DispatchResult*, Context*)); + bool read( + const std::string& oid, uint64_t object_no, uint64_t object_off, + uint64_t object_len, librados::snap_t snap_id, int op_flags, + const ZTracer::Trace& parent_trace, ceph::bufferlist* read_data, + ExtentMap* extent_map, int* dispatch_flags, + DispatchResult* dispatch_result, Context** on_finish, + Context* on_dispatched) { + return execute_read(object_no, object_off, object_len, snap_id, read_data, + extent_map, dispatch_result, on_dispatched); + } + + MOCK_METHOD9(execute_discard, + bool(uint64_t, uint64_t, uint64_t, const ::SnapContext &, int, + int*, uint64_t*, DispatchResult*, Context*)); + bool discard( + const std::string &oid, uint64_t object_no, uint64_t object_off, + uint64_t object_len, const ::SnapContext &snapc, int discard_flags, + const ZTracer::Trace &parent_trace, int* dispatch_flags, + uint64_t* journal_tid, DispatchResult* dispatch_result, + Context** on_finish, Context* on_dispatched) { + return execute_discard(object_no, object_off, object_len, snapc, + discard_flags, dispatch_flags, journal_tid, + dispatch_result, on_dispatched); + } + + MOCK_METHOD8(execute_write, + bool(uint64_t, uint64_t, const ceph::bufferlist&, + const ::SnapContext &, int*, uint64_t*, DispatchResult*, + Context *)); + bool write( + const std::string &oid, uint64_t object_no, uint64_t object_off, + ceph::bufferlist&& data, const ::SnapContext &snapc, int op_flags, + const ZTracer::Trace &parent_trace, int* dispatch_flags, + uint64_t* journal_tid, DispatchResult* dispatch_result, + Context** on_finish, Context* on_dispatched) override { + return execute_write(object_no, object_off, data, snapc, dispatch_flags, + journal_tid, dispatch_result, on_dispatched); + } + + MOCK_METHOD10(execute_write_same, + bool(uint64_t, uint64_t, uint64_t, const Extents&, + const ceph::bufferlist&, const ::SnapContext &, int*, + uint64_t*, DispatchResult*, Context *)); + bool write_same( + const std::string &oid, uint64_t object_no, uint64_t object_off, + uint64_t object_len, Extents&& buffer_extents, ceph::bufferlist&& data, + const ::SnapContext &snapc, int op_flags, + const ZTracer::Trace &parent_trace, int* dispatch_flags, + uint64_t* journal_tid, DispatchResult* dispatch_result, + Context* *on_finish, Context* on_dispatched) override { + return execute_write_same(object_no, object_off, object_len, buffer_extents, + data, snapc, dispatch_flags, journal_tid, + dispatch_result, on_dispatched); + } + + MOCK_METHOD9(execute_compare_and_write, + bool(uint64_t, uint64_t, const ceph::bufferlist&, + const ceph::bufferlist&, uint64_t*, int*, uint64_t*, + DispatchResult*, Context *)); + bool compare_and_write( + const std::string &oid, uint64_t object_no, uint64_t object_off, + ceph::bufferlist&& cmp_data, ceph::bufferlist&& write_data, + const ::SnapContext &snapc, int op_flags, + const ZTracer::Trace &parent_trace, uint64_t* mismatch_offset, + int* dispatch_flags, uint64_t* journal_tid, + DispatchResult* dispatch_result, Context** on_finish, + Context* on_dispatched) override { + return execute_compare_and_write(object_no, object_off, cmp_data, + write_data, mismatch_offset, + dispatch_flags, journal_tid, + dispatch_result, on_dispatched); + } + + MOCK_METHOD3(execute_flush, bool(FlushSource, DispatchResult*, + Context*)); + bool flush(FlushSource flush_source, const ZTracer::Trace &parent_trace, + DispatchResult* dispatch_result, Context** on_finish, + Context* on_dispatched) { + return execute_flush(flush_source, dispatch_result, on_dispatched); + } + + MOCK_METHOD1(invalidate_cache, bool(Context*)); + MOCK_METHOD1(reset_existence_cache, bool(Context*)); + + MOCK_METHOD5(extent_overwritten, void(uint64_t, uint64_t, uint64_t, uint64_t, + uint64_t)); +}; + +} // namespace io +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_IO_OBJECT_DISPATCH_H diff --git a/src/test/librbd/mock/io/MockObjectDispatcher.h b/src/test/librbd/mock/io/MockObjectDispatcher.h new file mode 100644 index 00000000..271c3010 --- /dev/null +++ b/src/test/librbd/mock/io/MockObjectDispatcher.h @@ -0,0 +1,42 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_IO_OBJECT_DISPATCHER_H +#define CEPH_TEST_LIBRBD_MOCK_IO_OBJECT_DISPATCHER_H + +#include "gmock/gmock.h" +#include "include/Context.h" +#include "librbd/io/ObjectDispatcher.h" +#include "librbd/io/ObjectDispatchSpec.h" +#include "librbd/io/Types.h" + +class Context; + +namespace librbd { +namespace io { + +struct ObjectDispatchInterface; + +struct MockObjectDispatcher : public ObjectDispatcherInterface { +public: + MOCK_METHOD1(shut_down, void(Context*)); + + MOCK_METHOD1(register_object_dispatch, void(ObjectDispatchInterface*)); + MOCK_METHOD2(shut_down_object_dispatch, void(ObjectDispatchLayer, Context*)); + + MOCK_METHOD2(flush, void(FlushSource, Context*)); + + MOCK_METHOD1(invalidate_cache, void(Context*)); + MOCK_METHOD1(reset_existance_cache, void(Context*)); + + MOCK_METHOD5(extent_overwritten, void(uint64_t, uint64_t, uint64_t, uint64_t, + uint64_t)); + + MOCK_METHOD1(send, void(ObjectDispatchSpec*)); + +}; + +} // namespace io +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_IO_OBJECT_DISPATCHER_H diff --git a/src/test/librbd/object_map/mock/MockInvalidateRequest.h b/src/test/librbd/object_map/mock/MockInvalidateRequest.h new file mode 100644 index 00000000..92f30748 --- /dev/null +++ b/src/test/librbd/object_map/mock/MockInvalidateRequest.h @@ -0,0 +1,41 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "librbd/object_map/InvalidateRequest.h" + +// template definitions +#include "librbd/object_map/InvalidateRequest.cc" + +namespace librbd { +namespace object_map { + +template <typename I> +struct MockInvalidateRequestBase { + static std::list<InvalidateRequest<I>*> s_requests; + uint64_t snap_id = 0; + bool force = false; + Context *on_finish = nullptr; + + static InvalidateRequest<I>* create(I &image_ctx, uint64_t snap_id, + bool force, Context *on_finish) { + ceph_assert(!s_requests.empty()); + InvalidateRequest<I>* req = s_requests.front(); + req->snap_id = snap_id; + req->force = force; + req->on_finish = on_finish; + s_requests.pop_front(); + return req; + } + + MockInvalidateRequestBase() { + s_requests.push_back(static_cast<InvalidateRequest<I>*>(this)); + } + + MOCK_METHOD0(send, void()); +}; + +template <typename I> +std::list<InvalidateRequest<I>*> MockInvalidateRequestBase<I>::s_requests; + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_InvalidateRequest.cc b/src/test/librbd/object_map/test_mock_InvalidateRequest.cc new file mode 100644 index 00000000..f4b2ed43 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_InvalidateRequest.cc @@ -0,0 +1,153 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "librbd/internal.h" +#include "librbd/api/Image.h" +#include "librbd/object_map/InvalidateRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapInvalidateRequest : public TestMockFixture { +public: +}; + +TEST_F(TestMockObjectMapInvalidateRequest, UpdatesInMemoryFlag) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + bool flags_set; + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, + RBD_FLAG_OBJECT_MAP_INVALID, &flags_set)); + ASSERT_FALSE(flags_set); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new InvalidateRequest<>(*ictx, CEPH_NOSNAP, false, &cond_ctx); + + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, _)) + .Times(0); + + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, + RBD_FLAG_OBJECT_MAP_INVALID, &flags_set)); + ASSERT_TRUE(flags_set); +} + +TEST_F(TestMockObjectMapInvalidateRequest, UpdatesHeadOnDiskFlag) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new InvalidateRequest<>(*ictx, CEPH_NOSNAP, false, &cond_ctx); + + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, _)) + .WillOnce(DoDefault()); + + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapInvalidateRequest, UpdatesSnapOnDiskFlag) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx, + cls::rbd::UserSnapshotNamespace(), + "snap1")); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new InvalidateRequest<>(*ictx, ictx->snap_id, false, + &cond_ctx); + + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, _)) + .WillOnce(DoDefault()); + + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockObjectMapInvalidateRequest, SkipOnDiskUpdateWithoutLock) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new InvalidateRequest<>(*ictx, CEPH_NOSNAP, false, &cond_ctx); + + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, _)) + .Times(0); + + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapInvalidateRequest, IgnoresOnDiskUpdateFailure) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new InvalidateRequest<>(*ictx, CEPH_NOSNAP, false, &cond_ctx); + + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, _)) + .WillOnce(Return(-EINVAL)); + + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_LockRequest.cc b/src/test/librbd/object_map/test_mock_LockRequest.cc new file mode 100644 index 00000000..5954a461 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_LockRequest.cc @@ -0,0 +1,220 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "cls/lock/cls_lock_ops.h" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/LockRequest.h" + +// template definitions +#include "librbd/object_map/LockRequest.cc" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockObjectMapLockRequest : public TestMockFixture { +public: + typedef LockRequest<MockImageCtx> MockLockRequest; + + void expect_lock(MockImageCtx &mock_image_ctx, int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + CEPH_NOSNAP)); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("lock"), StrEq("lock"), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_get_lock_info(MockImageCtx &mock_image_ctx, int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + CEPH_NOSNAP)); + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("lock"), StrEq("get_info"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + entity_name_t entity1(entity_name_t::CLIENT(1)); + entity_name_t entity2(entity_name_t::CLIENT(2)); + + cls_lock_get_info_reply reply; + reply.lockers.emplace( + rados::cls::lock::locker_id_t(entity1, "cookie1"), + rados::cls::lock::locker_info_t()); + reply.lockers.emplace( + rados::cls::lock::locker_id_t(entity2, "cookie2"), + rados::cls::lock::locker_info_t()); + + bufferlist bl; + encode(reply, bl, CEPH_FEATURES_SUPPORTED_DEFAULT); + + std::string str(bl.c_str(), bl.length()); + expect.WillOnce(DoAll(WithArg<5>(CopyInBufferlist(str)), Return(r))); + } + } + + void expect_break_lock(MockImageCtx &mock_image_ctx, int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + CEPH_NOSNAP)); + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("lock"), StrEq("break_lock"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.Times(2).WillRepeatedly(Return(0)); + } + } +}; + +TEST_F(TestMockObjectMapLockRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, 0); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, LockBusy) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, 0); + expect_lock(mock_image_ctx, 0); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, LockError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -ENOENT); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, GetLockInfoMissing) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, -ENOENT); + expect_lock(mock_image_ctx, 0); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, GetLockInfoError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, -EINVAL); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, BreakLockMissing) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, -ENOENT); + expect_lock(mock_image_ctx, 0); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, BreakLockError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, -EINVAL); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, LockErrorAfterBrokeLock) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, 0); + expect_lock(mock_image_ctx, -EBUSY); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_RefreshRequest.cc b/src/test/librbd/object_map/test_mock_RefreshRequest.cc new file mode 100644 index 00000000..60cc579a --- /dev/null +++ b/src/test/librbd/object_map/test_mock_RefreshRequest.cc @@ -0,0 +1,453 @@ +// -*- 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/object_map/mock/MockInvalidateRequest.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/RefreshRequest.h" +#include "librbd/object_map/LockRequest.h" + +namespace librbd { + +namespace { + +struct MockObjectMapImageCtx : public MockImageCtx { + MockObjectMapImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace object_map { + +template <> +class LockRequest<MockObjectMapImageCtx> { +public: + static LockRequest *s_instance; + static LockRequest *create(MockObjectMapImageCtx &image_ctx, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + LockRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template<> +struct InvalidateRequest<MockObjectMapImageCtx> : + public MockInvalidateRequestBase<MockObjectMapImageCtx> { +}; + +LockRequest<MockObjectMapImageCtx> *LockRequest<MockObjectMapImageCtx>::s_instance = nullptr; + +} // namespace object_map +} // namespace librbd + +// template definitions +#include "librbd/object_map/RefreshRequest.cc" +#include "librbd/object_map/LockRequest.cc" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockObjectMapRefreshRequest : public TestMockFixture { +public: + static const uint64_t TEST_SNAP_ID = 123; + + typedef RefreshRequest<MockObjectMapImageCtx> MockRefreshRequest; + typedef LockRequest<MockObjectMapImageCtx> MockLockRequest; + typedef InvalidateRequest<MockObjectMapImageCtx> MockInvalidateRequest; + + void expect_object_map_lock(MockObjectMapImageCtx &mock_image_ctx, + MockLockRequest &mock_lock_request) { + EXPECT_CALL(mock_lock_request, send()) + .WillOnce(FinishRequest(&mock_lock_request, 0, + &mock_image_ctx)); + } + + void expect_object_map_load(MockObjectMapImageCtx &mock_image_ctx, + ceph::BitVector<2> *object_map, uint64_t snap_id, + int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, snap_id)); + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_load"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + ceph_assert(object_map); + object_map->set_crc_enabled(false); + + bufferlist bl; + encode(*object_map, bl); + + std::string str(bl.c_str(), bl.length()); + expect.WillOnce(DoAll(WithArg<5>(CopyInBufferlist(str)), Return(0))); + } + } + + void expect_get_image_size(MockObjectMapImageCtx &mock_image_ctx, uint64_t snap_id, + uint64_t size) { + EXPECT_CALL(mock_image_ctx, get_image_size(snap_id)) + .WillOnce(Return(size)); + } + + void expect_invalidate_request(MockObjectMapImageCtx &mock_image_ctx, + MockInvalidateRequest &invalidate_request, + int r) { + EXPECT_CALL(invalidate_request, send()) + .WillOnce(FinishRequest(&invalidate_request, r, + &mock_image_ctx)); + } + + void expect_truncate_request(MockObjectMapImageCtx &mock_image_ctx) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + TEST_SNAP_ID)); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), truncate(oid, 0, _)) + .WillOnce(Return(0)); + } + + void expect_object_map_resize(MockObjectMapImageCtx &mock_image_ctx, + uint64_t num_objects, int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + TEST_SNAP_ID)); + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_resize"), _, _, _)); + expect.WillOnce(Return(r)); + } + + void init_object_map(MockObjectMapImageCtx &mock_image_ctx, + ceph::BitVector<2> *object_map) { + uint64_t num_objs = Striper::get_num_objects( + mock_image_ctx.layout, mock_image_ctx.image_ctx->size); + object_map->resize(num_objs); + for (uint64_t i = 0; i < num_objs; ++i) { + (*object_map)[i] = rand() % 3; + } + } +}; + +TEST_F(TestMockObjectMapRefreshRequest, SuccessHead) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::BitVector<2> object_map; + MockLockRequest mock_lock_request; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map, + CEPH_NOSNAP, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, CEPH_NOSNAP, + mock_image_ctx.image_ctx->size); + expect_object_map_lock(mock_image_ctx, mock_lock_request); + expect_object_map_load(mock_image_ctx, &on_disk_object_map, CEPH_NOSNAP, 0); + expect_get_image_size(mock_image_ctx, CEPH_NOSNAP, + mock_image_ctx.image_ctx->size); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_EQ(on_disk_object_map, object_map); +} + +TEST_F(TestMockObjectMapRefreshRequest, SuccessSnapshot) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map, + TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, &on_disk_object_map, TEST_SNAP_ID, 0); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_EQ(on_disk_object_map, object_map); +} + +TEST_F(TestMockObjectMapRefreshRequest, LoadError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map, + TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, nullptr, TEST_SNAP_ID, -ENOENT); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, 0); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, LoadInvalidateError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map, + TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, nullptr, TEST_SNAP_ID, -ENOENT); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, -EPERM); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, LoadCorrupt) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map, + TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, nullptr, TEST_SNAP_ID, -EINVAL); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, 0); + expect_truncate_request(mock_image_ctx); + expect_object_map_resize(mock_image_ctx, on_disk_object_map.size(), 0); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, TooSmall) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + ceph::BitVector<2> small_object_map; + + C_SaferCond ctx; + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map, + TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, &small_object_map, TEST_SNAP_ID, 0); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, 0); + expect_object_map_resize(mock_image_ctx, on_disk_object_map.size(), 0); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, TooSmallInvalidateError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + ceph::BitVector<2> small_object_map; + + C_SaferCond ctx; + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map, + TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, &small_object_map, TEST_SNAP_ID, 0); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, -EPERM); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, TooLarge) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + ceph::BitVector<2> large_object_map; + large_object_map.resize(on_disk_object_map.size() * 2); + + C_SaferCond ctx; + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map, + TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, &large_object_map, TEST_SNAP_ID, 0); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, ResizeError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + ceph::BitVector<2> small_object_map; + + C_SaferCond ctx; + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map, + TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, &small_object_map, TEST_SNAP_ID, 0); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, 0); + expect_object_map_resize(mock_image_ctx, on_disk_object_map.size(), -ESTALE); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, LargeImageError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map, + TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + std::numeric_limits<int64_t>::max()); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, 0); + + req->send(); + ASSERT_EQ(-EFBIG, ctx.wait()); +} + +} // namespace object_map +} // namespace librbd + diff --git a/src/test/librbd/object_map/test_mock_ResizeRequest.cc b/src/test/librbd/object_map/test_mock_ResizeRequest.cc new file mode 100644 index 00000000..3cfe34cf --- /dev/null +++ b/src/test/librbd/object_map/test_mock_ResizeRequest.cc @@ -0,0 +1,146 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/api/Image.h" +#include "librbd/object_map/ResizeRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapResizeRequest : public TestMockFixture { +public: + void expect_resize(librbd::ImageCtx *ictx, uint64_t snap_id, int r) { + std::string oid(ObjectMap<>::object_map_name(ictx->id, snap_id)); + if (snap_id == CEPH_NOSNAP) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _)) + .WillOnce(DoDefault()); + } + + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_resize"), _, _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_resize"), _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_invalidate(librbd::ImageCtx *ictx) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, _)) + .WillOnce(DoDefault()); + } +}; + +TEST_F(TestMockObjectMapResizeRequest, UpdateInMemory) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &object_map, CEPH_NOSNAP, object_map.size(), OBJECT_EXISTS, + &cond_ctx); + req->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + for (uint64_t i = 0; i < object_map.size(); ++i) { + ASSERT_EQ(i == 0 ? OBJECT_NONEXISTENT : OBJECT_EXISTS, + object_map[i]); + } +} + +TEST_F(TestMockObjectMapResizeRequest, UpdateHeadOnDisk) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + expect_resize(ictx, CEPH_NOSNAP, 0); + + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &object_map, CEPH_NOSNAP, object_map.size(), OBJECT_EXISTS, + &cond_ctx); + req->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapResizeRequest, UpdateSnapOnDisk) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx, + cls::rbd::UserSnapshotNamespace(), + "snap1")); + + uint64_t snap_id = ictx->snap_id; + expect_resize(ictx, snap_id, 0); + + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &object_map, snap_id, object_map.size(), OBJECT_EXISTS, + &cond_ctx); + req->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapResizeRequest, UpdateOnDiskError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + expect_resize(ictx, CEPH_NOSNAP, -EINVAL); + expect_invalidate(ictx); + + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &object_map, CEPH_NOSNAP, object_map.size(), OBJECT_EXISTS, + &cond_ctx); + req->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_SnapshotCreateRequest.cc b/src/test/librbd/object_map/test_mock_SnapshotCreateRequest.cc new file mode 100644 index 00000000..6aa8d142 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_SnapshotCreateRequest.cc @@ -0,0 +1,224 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "cls/rbd/cls_rbd_types.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/SnapshotCreateRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapSnapshotCreateRequest : public TestMockFixture { +public: + void inject_snap_info(librbd::ImageCtx *ictx, uint64_t snap_id) { + RWLock::WLocker snap_locker(ictx->snap_lock); + RWLock::RLocker parent_locker(ictx->parent_lock); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "snap name", snap_id, + ictx->size, ictx->parent_md, + RBD_PROTECTION_STATUS_UNPROTECTED, 0, utime_t()); + } + + void expect_read_map(librbd::ImageCtx *ictx, int r) { + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + read(ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP), + 0, 0, _)).WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + read(ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP), + 0, 0, _)).WillOnce(DoDefault()); + } + } + + void expect_write_map(librbd::ImageCtx *ictx, uint64_t snap_id, int r) { + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + write_full( + ObjectMap<>::object_map_name(ictx->id, snap_id), _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + write_full( + ObjectMap<>::object_map_name(ictx->id, snap_id), _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_add_snapshot(librbd::ImageCtx *ictx, int r) { + std::string oid(ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP)); + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _)) + .WillOnce(DoDefault()); + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_snap_add"), _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_invalidate(librbd::ImageCtx *ictx) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, _)) + .WillOnce(DoDefault()); + } +}; + +TEST_F(TestMockObjectMapSnapshotCreateRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::BitVector<2> object_map; + + uint64_t snap_id = 1; + inject_snap_info(ictx, snap_id); + expect_read_map(ictx, 0); + expect_write_map(ictx, snap_id, 0); + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + expect_add_snapshot(ictx, 0); + } + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotCreateRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotCreateRequest, ReadMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::BitVector<2> object_map; + + uint64_t snap_id = 1; + inject_snap_info(ictx, snap_id); + expect_read_map(ictx, -ENOENT); + expect_invalidate(ictx); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotCreateRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotCreateRequest, WriteMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::BitVector<2> object_map; + + uint64_t snap_id = 1; + inject_snap_info(ictx, snap_id); + expect_read_map(ictx, 0); + expect_write_map(ictx, snap_id, -EINVAL); + expect_invalidate(ictx); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotCreateRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotCreateRequest, AddSnapshotError) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::BitVector<2> object_map; + + uint64_t snap_id = 1; + inject_snap_info(ictx, snap_id); + expect_read_map(ictx, 0); + expect_write_map(ictx, snap_id, 0); + expect_add_snapshot(ictx, -EINVAL); + expect_invalidate(ictx); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotCreateRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotCreateRequest, FlagCleanObjects) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::BitVector<2> object_map; + object_map.resize(1024); + for (uint64_t i = 0; i < object_map.size(); ++i) { + object_map[i] = i % 2 == 0 ? OBJECT_EXISTS : OBJECT_NONEXISTENT; + } + + uint64_t snap_id = 1; + inject_snap_info(ictx, snap_id); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotCreateRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + for (uint64_t i = 0; i < object_map.size(); ++i) { + ASSERT_EQ(i % 2 == 0 ? OBJECT_EXISTS_CLEAN : OBJECT_NONEXISTENT, + object_map[i]); + } +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc b/src/test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc new file mode 100644 index 00000000..b9dd8168 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc @@ -0,0 +1,330 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/SnapshotRemoveRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapSnapshotRemoveRequest : public TestMockFixture { +public: + void expect_load_map(librbd::ImageCtx *ictx, uint64_t snap_id, int r) { + std::string snap_oid(ObjectMap<>::object_map_name(ictx->id, snap_id)); + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(snap_oid, _, StrEq("rbd"), StrEq("object_map_load"), _, _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(snap_oid, _, StrEq("rbd"), StrEq("object_map_load"), _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_remove_snapshot(librbd::ImageCtx *ictx, int r) { + std::string oid(ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP)); + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _)) + .WillOnce(DoDefault()); + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_snap_remove"), _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_remove_map(librbd::ImageCtx *ictx, uint64_t snap_id, int r) { + std::string snap_oid(ObjectMap<>::object_map_name(ictx->id, snap_id)); + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), remove(snap_oid, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), remove(snap_oid, _)) + .WillOnce(DoDefault()); + } + } + + void expect_invalidate(librbd::ImageCtx *ictx) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, _)) + .WillOnce(DoDefault()); + } +}; + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + expect_load_map(ictx, snap_id, 0); + expect_remove_snapshot(ictx, 0); + } + expect_remove_map(ictx, snap_id, 0); + + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, LoadMapMissing) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + auto snap_it = ictx->snap_info.find(snap_id); + ASSERT_NE(ictx->snap_info.end(), snap_it); + snap_it->second.flags |= RBD_FLAG_OBJECT_MAP_INVALID; + + expect_load_map(ictx, snap_id, -ENOENT); + + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + { + // shouldn't invalidate the HEAD revision when we fail to load + // the already deleted snapshot + RWLock::RLocker snap_locker(ictx->snap_lock); + uint64_t flags; + ASSERT_EQ(0, ictx->get_flags(CEPH_NOSNAP, &flags)); + ASSERT_EQ(0U, flags & RBD_FLAG_OBJECT_MAP_INVALID); + } + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, LoadMapError) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_load_map(ictx, snap_id, -EINVAL); + expect_invalidate(ictx); + expect_remove_map(ictx, snap_id, 0); + + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, RemoveSnapshotMissing) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_load_map(ictx, snap_id, 0); + expect_remove_snapshot(ictx, -ENOENT); + expect_remove_map(ictx, snap_id, 0); + + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, RemoveSnapshotError) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_load_map(ictx, snap_id, 0); + expect_remove_snapshot(ictx, -EINVAL); + expect_invalidate(ictx); + expect_remove_map(ictx, snap_id, 0); + + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, RemoveMapMissing) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + expect_load_map(ictx, snap_id, 0); + expect_remove_snapshot(ictx, 0); + } + expect_remove_map(ictx, snap_id, -ENOENT); + + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, RemoveMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + expect_load_map(ictx, snap_id, 0); + expect_remove_snapshot(ictx, 0); + } + expect_remove_map(ictx, snap_id, -EINVAL); + + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, ScrubCleanObjects) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + librbd::NoOpProgressContext prog_ctx; + uint64_t size = 4294967296; // 4GB = 1024 * 4MB + ASSERT_EQ(0, resize(ictx, size)); + + // update image objectmap for snap inherit + ceph::BitVector<2> object_map; + object_map.resize(1024); + for (uint64_t i = 512; i < object_map.size(); ++i) { + object_map[i] = i % 2 == 0 ? OBJECT_EXISTS : OBJECT_NONEXISTENT; + } + + C_SaferCond cond_ctx1; + { + librbd::ObjectMap om(*ictx, ictx->snap_id); + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + om.set_object_map(object_map); + om.aio_save(&cond_ctx1); + } + ASSERT_EQ(0, cond_ctx1.wait()); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + // simutate the image objectmap state after creating snap + for (uint64_t i = 512; i < object_map.size(); ++i) { + object_map[i] = i % 2 == 0 ? OBJECT_EXISTS_CLEAN : OBJECT_NONEXISTENT; + } + + C_SaferCond cond_ctx2; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx2); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx2.wait()); + + for (uint64_t i = 512; i < object_map.size(); ++i) { + ASSERT_EQ(i % 2 == 0 ? OBJECT_EXISTS : OBJECT_NONEXISTENT, + object_map[i]); + } +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc b/src/test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc new file mode 100644 index 00000000..19d89d10 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc @@ -0,0 +1,147 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/SnapshotRollbackRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapSnapshotRollbackRequest : public TestMockFixture { +public: + void expect_read_map(librbd::ImageCtx *ictx, uint64_t snap_id, int r) { + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + read(ObjectMap<>::object_map_name(ictx->id, snap_id), + 0, 0, _)).WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + read(ObjectMap<>::object_map_name(ictx->id, snap_id), + 0, 0, _)).WillOnce(DoDefault()); + } + } + + void expect_write_map(librbd::ImageCtx *ictx, int r) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP), _, + StrEq("lock"), StrEq("assert_locked"), _, _, _)) + .WillOnce(DoDefault()); + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + write_full( + ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP), _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + write_full( + ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP), _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_invalidate(librbd::ImageCtx *ictx, uint32_t times) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, _)) + .Times(times) + .WillRepeatedly(DoDefault()); + } +}; + +TEST_F(TestMockObjectMapSnapshotRollbackRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_read_map(ictx, snap_id, 0); + expect_write_map(ictx, 0); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRollbackRequest( + *ictx, snap_id, &cond_ctx); + request->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRollbackRequest, ReadMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_read_map(ictx, snap_id, -ENOENT); + expect_invalidate(ictx, 2); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRollbackRequest( + *ictx, snap_id, &cond_ctx); + request->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + { + RWLock::RLocker snap_locker(ictx->snap_lock); + uint64_t flags; + ASSERT_EQ(0, ictx->get_flags(snap_id, &flags)); + ASSERT_NE(0U, flags & RBD_FLAG_OBJECT_MAP_INVALID); + } + bool flags_set; + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, + RBD_FLAG_OBJECT_MAP_INVALID, &flags_set)); + ASSERT_TRUE(flags_set); + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRollbackRequest, WriteMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_read_map(ictx, snap_id, 0); + expect_write_map(ictx, -EINVAL); + expect_invalidate(ictx, 1); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRollbackRequest( + *ictx, snap_id, &cond_ctx); + request->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + { + RWLock::RLocker snap_locker(ictx->snap_lock); + uint64_t flags; + ASSERT_EQ(0, ictx->get_flags(snap_id, &flags)); + ASSERT_EQ(0U, flags & RBD_FLAG_OBJECT_MAP_INVALID); + } + bool flags_set; + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, + RBD_FLAG_OBJECT_MAP_INVALID, &flags_set)); + ASSERT_TRUE(flags_set); + expect_unlock_exclusive_lock(*ictx); +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_UnlockRequest.cc b/src/test/librbd/object_map/test_mock_UnlockRequest.cc new file mode 100644 index 00000000..95879c88 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_UnlockRequest.cc @@ -0,0 +1,69 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "cls/lock/cls_lock_ops.h" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/UnlockRequest.h" + +// template definitions +#include "librbd/object_map/UnlockRequest.cc" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapUnlockRequest : public TestMockFixture { +public: + typedef UnlockRequest<MockImageCtx> MockUnlockRequest; + + void expect_unlock(MockImageCtx &mock_image_ctx, int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + CEPH_NOSNAP)); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("lock"), StrEq("unlock"), _, _, _)) + .WillOnce(Return(r)); + } +}; + +TEST_F(TestMockObjectMapUnlockRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockUnlockRequest *req = new MockUnlockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_unlock(mock_image_ctx, 0); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapUnlockRequest, UnlockError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockUnlockRequest *req = new MockUnlockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_unlock(mock_image_ctx, -ENOENT); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_UpdateRequest.cc b/src/test/librbd/object_map/test_mock_UpdateRequest.cc new file mode 100644 index 00000000..b7de4d18 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_UpdateRequest.cc @@ -0,0 +1,280 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/Operations.h" +#include "librbd/api/Image.h" +#include "librbd/object_map/UpdateRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapUpdateRequest : public TestMockFixture { +public: + void expect_update(librbd::ImageCtx *ictx, uint64_t snap_id, + uint64_t start_object_no, uint64_t end_object_no, + uint8_t new_state, + const boost::optional<uint8_t>& current_state, int r) { + bufferlist bl; + encode(start_object_no, bl); + encode(end_object_no, bl); + encode(new_state, bl); + encode(current_state, bl); + + std::string oid(ObjectMap<>::object_map_name(ictx->id, snap_id)); + if (snap_id == CEPH_NOSNAP) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _)) + .WillOnce(DoDefault()); + } + + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_update"), + ContentsEqual(bl), _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_update"), + ContentsEqual(bl), _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_invalidate(librbd::ImageCtx *ictx) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, _)) + .WillOnce(DoDefault()); + } +}; + +TEST_F(TestMockObjectMapUpdateRequest, UpdateInMemory) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + librbd::NoOpProgressContext no_progress; + ASSERT_EQ(0, ictx->operations->resize(4 << ictx->order, true, no_progress)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::BitVector<2> object_map; + object_map.resize(4); + for (uint64_t i = 0; i < object_map.size(); ++i) { + object_map[i] = i % 4; + } + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->object_map_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + for (uint64_t i = 0; i < object_map.size(); ++i) { + if (i % 4 == OBJECT_EXISTS || i % 4 == OBJECT_EXISTS_CLEAN) { + ASSERT_EQ(OBJECT_NONEXISTENT, object_map[i]); + } else { + ASSERT_EQ(i % 4, object_map[i]); + } + } +} + +TEST_F(TestMockObjectMapUpdateRequest, UpdateHeadOnDisk) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + expect_update(ictx, CEPH_NOSNAP, 0, 1, OBJECT_NONEXISTENT, OBJECT_EXISTS, 0); + + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->object_map_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapUpdateRequest, UpdateSnapOnDisk) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx, + cls::rbd::UserSnapshotNamespace(), + "snap1")); + + uint64_t snap_id = ictx->snap_id; + expect_update(ictx, snap_id, 0, 1, OBJECT_NONEXISTENT, OBJECT_EXISTS, 0); + + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, snap_id, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->object_map_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapUpdateRequest, UpdateOnDiskError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + expect_update(ictx, CEPH_NOSNAP, 0, 1, OBJECT_NONEXISTENT, OBJECT_EXISTS, + -EINVAL); + expect_invalidate(ictx); + + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->object_map_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapUpdateRequest, RebuildSnapOnDisk) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + ASSERT_EQ(CEPH_NOSNAP, ictx->snap_id); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_update(ictx, snap_id, 0, 1, OBJECT_EXISTS_CLEAN, + boost::optional<uint8_t>(), 0); + expect_unlock_exclusive_lock(*ictx); + + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, snap_id, 0, object_map.size(), OBJECT_EXISTS_CLEAN, + boost::optional<uint8_t>(), {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->object_map_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + // do not update the in-memory map if rebuilding a snapshot + ASSERT_NE(OBJECT_EXISTS_CLEAN, object_map[0]); +} + +TEST_F(TestMockObjectMapUpdateRequest, BatchUpdate) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + librbd::NoOpProgressContext no_progress; + ASSERT_EQ(0, ictx->operations->resize(712312 * ictx->get_object_size(), false, + no_progress)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + InSequence seq; + expect_update(ictx, CEPH_NOSNAP, 0, 262144, OBJECT_NONEXISTENT, OBJECT_EXISTS, + 0); + expect_update(ictx, CEPH_NOSNAP, 262144, 524288, OBJECT_NONEXISTENT, + OBJECT_EXISTS, 0); + expect_update(ictx, CEPH_NOSNAP, 524288, 712312, OBJECT_NONEXISTENT, + OBJECT_EXISTS, 0); + expect_unlock_exclusive_lock(*ictx); + + ceph::BitVector<2> object_map; + object_map.resize(712312); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->object_map_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockObjectMapUpdateRequest, IgnoreMissingObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + expect_update(ictx, CEPH_NOSNAP, 0, 1, OBJECT_NONEXISTENT, OBJECT_EXISTS, + -ENOENT); + + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, true, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->object_map_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_DisableFeaturesRequest.cc b/src/test/librbd/operation/test_mock_DisableFeaturesRequest.cc new file mode 100644 index 00000000..e7843586 --- /dev/null +++ b/src/test/librbd/operation/test_mock_DisableFeaturesRequest.cc @@ -0,0 +1,526 @@ +// -*- 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/MockJournalPolicy.h" +#include "cls/rbd/cls_rbd_client.h" +#include "librbd/internal.h" +#include "librbd/image/SetFlagsRequest.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/mirror/DisableRequest.h" +#include "librbd/journal/RemoveRequest.h" +#include "librbd/journal/StandardPolicy.h" +#include "librbd/journal/Types.h" +#include "librbd/object_map/RemoveRequest.h" +#include "librbd/operation/DisableFeaturesRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { + +namespace { + +struct MockOperationImageCtx : public MockImageCtx { + MockOperationImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace image { + +template<> +class SetFlagsRequest<MockOperationImageCtx> { +public: + static SetFlagsRequest *s_instance; + Context *on_finish = nullptr; + + static SetFlagsRequest *create(MockOperationImageCtx *image_ctx, uint64_t flags, + uint64_t mask, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SetFlagsRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +SetFlagsRequest<MockOperationImageCtx> *SetFlagsRequest<MockOperationImageCtx>::s_instance; + +} // namespace image + +namespace journal { + +template<> +class RemoveRequest<MockOperationImageCtx> { +public: + static RemoveRequest *s_instance; + Context *on_finish = nullptr; + + static RemoveRequest *create(IoCtx &ioctx, const std::string &imageid, + const std::string &client_id, + MockContextWQ *op_work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + RemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +RemoveRequest<MockOperationImageCtx> *RemoveRequest<MockOperationImageCtx>::s_instance; + +template<> +class StandardPolicy<MockOperationImageCtx> : public MockJournalPolicy { +public: + StandardPolicy(MockOperationImageCtx* image_ctx) { + } +}; + +} // namespace journal + +namespace mirror { + +template<> +class DisableRequest<MockOperationImageCtx> { +public: + static DisableRequest *s_instance; + Context *on_finish = nullptr; + + static DisableRequest *create(MockOperationImageCtx *image_ctx, bool force, + bool remove, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + DisableRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +DisableRequest<MockOperationImageCtx> *DisableRequest<MockOperationImageCtx>::s_instance; + +} // namespace mirror + +namespace object_map { + +template<> +class RemoveRequest<MockOperationImageCtx> { +public: + static RemoveRequest *s_instance; + Context *on_finish = nullptr; + + static RemoveRequest *create(MockOperationImageCtx *image_ctx, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + RemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +RemoveRequest<MockOperationImageCtx> *RemoveRequest<MockOperationImageCtx>::s_instance; + +} // namespace object_map + +template <> +struct AsyncRequest<MockOperationImageCtx> : public AsyncRequest<MockImageCtx> { + MockOperationImageCtx &m_image_ctx; + + AsyncRequest(MockOperationImageCtx &image_ctx, Context *on_finish) + : AsyncRequest<MockImageCtx>(image_ctx, on_finish), m_image_ctx(image_ctx) { + } +}; + +} // namespace librbd + +// template definitions +#include "librbd/AsyncRequest.cc" +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/operation/Request.cc" +#include "librbd/operation/DisableFeaturesRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::_; + +class TestMockOperationDisableFeaturesRequest : public TestMockFixture { +public: + typedef librbd::image::SetFlagsRequest<MockOperationImageCtx> MockSetFlagsRequest; + typedef librbd::journal::RemoveRequest<MockOperationImageCtx> MockRemoveJournalRequest; + typedef librbd::mirror::DisableRequest<MockOperationImageCtx> MockDisableMirrorRequest; + typedef librbd::object_map::RemoveRequest<MockOperationImageCtx> MockRemoveObjectMapRequest; + typedef DisableFeaturesRequest<MockOperationImageCtx> MockDisableFeaturesRequest; + + class PoolMirrorModeEnabler { + public: + PoolMirrorModeEnabler(librados::IoCtx &ioctx) : m_ioctx(ioctx) { + EXPECT_EQ(0, librbd::cls_client::mirror_uuid_set(&m_ioctx, "test-uuid")); + EXPECT_EQ(0, librbd::cls_client::mirror_mode_set( + &m_ioctx, cls::rbd::MIRROR_MODE_POOL)); + } + + ~PoolMirrorModeEnabler() { + EXPECT_EQ(0, librbd::cls_client::mirror_mode_set( + &m_ioctx, cls::rbd::MIRROR_MODE_DISABLED)); + } + private: + librados::IoCtx &m_ioctx; + }; + + void expect_prepare_lock(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, prepare_lock(_)) + .WillOnce(Invoke([](Context *on_ready) { + on_ready->complete(0); + })); + expect_op_work_queue(mock_image_ctx); + } + + void expect_handle_prepare_lock_complete(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, handle_prepare_lock_complete()); + } + + void expect_block_writes(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, block_writes(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_unblock_writes(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, unblock_writes()).Times(1); + } + + void expect_verify_lock_ownership(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillRepeatedly(Return(true)); + } + } + + void expect_block_requests(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, block_requests(0)).Times(1); + } + } + + void expect_unblock_requests(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, unblock_requests()).Times(1); + } + } + + void expect_set_flags_request_send( + MockOperationImageCtx &mock_image_ctx, + MockSetFlagsRequest &mock_set_flags_request, int r) { + EXPECT_CALL(mock_set_flags_request, send()) + .WillOnce(FinishRequest(&mock_set_flags_request, r, + &mock_image_ctx)); + } + + void expect_disable_mirror_request_send( + MockOperationImageCtx &mock_image_ctx, + MockDisableMirrorRequest &mock_disable_mirror_request, int r) { + EXPECT_CALL(mock_disable_mirror_request, send()) + .WillOnce(FinishRequest(&mock_disable_mirror_request, r, + &mock_image_ctx)); + } + + void expect_close_journal(MockOperationImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.journal, close(_)) + .WillOnce(Invoke([&mock_image_ctx, r](Context *on_finish) { + mock_image_ctx.journal = nullptr; + mock_image_ctx.image_ctx->op_work_queue->queue(on_finish, r); + })); + } + + void expect_remove_journal_request_send( + MockOperationImageCtx &mock_image_ctx, + MockRemoveJournalRequest &mock_remove_journal_request, int r) { + EXPECT_CALL(mock_remove_journal_request, send()) + .WillOnce(FinishRequest(&mock_remove_journal_request, r, + &mock_image_ctx)); + } + + void expect_remove_object_map_request_send( + MockOperationImageCtx &mock_image_ctx, + MockRemoveObjectMapRequest &mock_remove_object_map_request, int r) { + EXPECT_CALL(mock_remove_object_map_request, send()) + .WillOnce(FinishRequest(&mock_remove_object_map_request, r, + &mock_image_ctx)); + } + + void expect_notify_update(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, notify_update(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + +}; + +TEST_F(TestMockOperationDisableFeaturesRequest, All) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + uint64_t features_to_disable = RBD_FEATURES_MUTABLE & features; + + REQUIRE(features_to_disable); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal_stack; + MockJournal *mock_journal = &mock_journal_stack; + if (features_to_disable & RBD_FEATURE_JOURNALING) { + mock_journal = new MockJournal(); + } + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, *mock_journal, + mock_object_map); + + expect_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockRemoveJournalRequest mock_remove_journal_request; + MockDisableMirrorRequest mock_disable_mirror_request; + MockRemoveObjectMapRequest mock_remove_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + if (features_to_disable & RBD_FEATURE_JOURNALING) { + expect_disable_mirror_request_send(mock_image_ctx, + mock_disable_mirror_request, 0); + expect_close_journal(mock_image_ctx, 0); + expect_remove_journal_request_send(mock_image_ctx, + mock_remove_journal_request, 0); + } + if (features_to_disable & RBD_FEATURE_OBJECT_MAP) { + expect_remove_object_map_request_send(mock_image_ctx, + mock_remove_object_map_request, 0); + } + if (features_to_disable & (RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF)) { + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, 0); + } + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockDisableFeaturesRequest *req = new MockDisableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, features_to_disable, false); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationDisableFeaturesRequest, ObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockRemoveObjectMapRequest mock_remove_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + expect_append_op_event(mock_image_ctx, true, 0); + expect_remove_object_map_request_send(mock_image_ctx, + mock_remove_object_map_request, 0); + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, 0); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + MockDisableFeaturesRequest *req = new MockDisableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, + RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF, false); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationDisableFeaturesRequest, ObjectMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockRemoveObjectMapRequest mock_remove_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + expect_append_op_event(mock_image_ctx, true, 0); + expect_remove_object_map_request_send(mock_image_ctx, + mock_remove_object_map_request, -EINVAL); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockDisableFeaturesRequest *req = new MockDisableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, + RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF, false); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationDisableFeaturesRequest, Mirroring) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal *mock_journal = new MockJournal(); + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, *mock_journal, + mock_object_map); + + expect_verify_lock_ownership(mock_image_ctx); + + MockRemoveJournalRequest mock_remove_journal_request; + MockDisableMirrorRequest mock_disable_mirror_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_is_journal_replaying(*mock_image_ctx.journal); + expect_block_requests(mock_image_ctx); + expect_disable_mirror_request_send(mock_image_ctx, + mock_disable_mirror_request, 0); + expect_close_journal(mock_image_ctx, 0); + expect_remove_journal_request_send(mock_image_ctx, + mock_remove_journal_request, 0); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockDisableFeaturesRequest *req = new MockDisableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_JOURNALING, false); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationDisableFeaturesRequest, MirroringError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal *mock_journal = new MockJournal(); + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, *mock_journal, + mock_object_map); + + expect_verify_lock_ownership(mock_image_ctx); + + MockRemoveJournalRequest mock_remove_journal_request; + MockDisableMirrorRequest mock_disable_mirror_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_is_journal_replaying(*mock_image_ctx.journal); + expect_block_requests(mock_image_ctx); + expect_disable_mirror_request_send(mock_image_ctx, + mock_disable_mirror_request, -EINVAL); + expect_close_journal(mock_image_ctx, 0); + expect_remove_journal_request_send(mock_image_ctx, + mock_remove_journal_request, 0); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockDisableFeaturesRequest *req = new MockDisableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_JOURNALING, false); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_EnableFeaturesRequest.cc b/src/test/librbd/operation/test_mock_EnableFeaturesRequest.cc new file mode 100644 index 00000000..1bb9bdbd --- /dev/null +++ b/src/test/librbd/operation/test_mock_EnableFeaturesRequest.cc @@ -0,0 +1,627 @@ +// -*- 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 "cls/rbd/cls_rbd_client.h" +#include "librbd/Operations.h" +#include "librbd/internal.h" +#include "librbd/image/SetFlagsRequest.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/mirror/EnableRequest.h" +#include "librbd/journal/CreateRequest.h" +#include "librbd/journal/Types.h" +#include "librbd/object_map/CreateRequest.h" +#include "librbd/operation/EnableFeaturesRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { + +namespace { + +struct MockOperationImageCtx : public MockImageCtx { + MockOperationImageCtx(librbd::ImageCtx& image_ctx) + : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace image { + +template<> +class SetFlagsRequest<MockOperationImageCtx> { +public: + static SetFlagsRequest *s_instance; + Context *on_finish = nullptr; + + static SetFlagsRequest *create(MockOperationImageCtx *image_ctx, uint64_t flags, + uint64_t mask, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SetFlagsRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +SetFlagsRequest<MockOperationImageCtx> *SetFlagsRequest<MockOperationImageCtx>::s_instance; + +} // namespace image + +namespace journal { + +template<> +class CreateRequest<MockOperationImageCtx> { +public: + static CreateRequest *s_instance; + Context *on_finish = nullptr; + + static CreateRequest *create(IoCtx &ioctx, const std::string &imageid, + uint8_t order, uint8_t splay_width, + const std::string &object_pool, + uint64_t tag_class, TagData &tag_data, + const std::string &client_id, + MockContextWQ *op_work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + CreateRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +CreateRequest<MockOperationImageCtx> *CreateRequest<MockOperationImageCtx>::s_instance = nullptr; + +} // namespace journal + +namespace mirror { + +template<> +class EnableRequest<MockOperationImageCtx> { +public: + static EnableRequest *s_instance; + Context *on_finish = nullptr; + + static EnableRequest *create(MockOperationImageCtx *image_ctx, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + EnableRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +EnableRequest<MockOperationImageCtx> *EnableRequest<MockOperationImageCtx>::s_instance = nullptr; + +} // namespace mirror + +namespace object_map { + +template<> +class CreateRequest<MockOperationImageCtx> { +public: + static CreateRequest *s_instance; + Context *on_finish = nullptr; + + static CreateRequest *create(MockOperationImageCtx *image_ctx, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + CreateRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +CreateRequest<MockOperationImageCtx> *CreateRequest<MockOperationImageCtx>::s_instance = nullptr; + +} // namespace object_map + +template <> +struct AsyncRequest<MockOperationImageCtx> : public AsyncRequest<MockImageCtx> { + MockOperationImageCtx &m_image_ctx; + + AsyncRequest(MockOperationImageCtx &image_ctx, Context *on_finish) + : AsyncRequest<MockImageCtx>(image_ctx, on_finish), m_image_ctx(image_ctx) { + } +}; + +} // namespace librbd + +// template definitions +#include "librbd/AsyncRequest.cc" +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/operation/Request.cc" +#include "librbd/operation/EnableFeaturesRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::_; + +class TestMockOperationEnableFeaturesRequest : public TestMockFixture { +public: + typedef librbd::image::SetFlagsRequest<MockOperationImageCtx> MockSetFlagsRequest; + typedef librbd::journal::CreateRequest<MockOperationImageCtx> MockCreateJournalRequest; + typedef librbd::mirror::EnableRequest<MockOperationImageCtx> MockEnableMirrorRequest; + typedef librbd::object_map::CreateRequest<MockOperationImageCtx> MockCreateObjectMapRequest; + typedef EnableFeaturesRequest<MockOperationImageCtx> MockEnableFeaturesRequest; + + class PoolMirrorModeEnabler { + public: + PoolMirrorModeEnabler(librados::IoCtx &ioctx) : m_ioctx(ioctx) { + EXPECT_EQ(0, librbd::cls_client::mirror_uuid_set(&m_ioctx, "test-uuid")); + EXPECT_EQ(0, librbd::cls_client::mirror_mode_set( + &m_ioctx, cls::rbd::MIRROR_MODE_POOL)); + } + + ~PoolMirrorModeEnabler() { + EXPECT_EQ(0, librbd::cls_client::mirror_mode_set( + &m_ioctx, cls::rbd::MIRROR_MODE_DISABLED)); + } + private: + librados::IoCtx &m_ioctx; + }; + + void ensure_features_disabled(librbd::ImageCtx *ictx, + uint64_t features_to_disable) { + uint64_t features; + + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + features_to_disable &= features; + if (!features_to_disable) { + return; + } + ASSERT_EQ(0, ictx->operations->update_features(features_to_disable, false)); + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + ASSERT_EQ(0U, features & features_to_disable); + } + + void expect_prepare_lock(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, prepare_lock(_)) + .WillOnce(Invoke([](Context *on_ready) { + on_ready->complete(0); + })); + expect_op_work_queue(mock_image_ctx); + } + + void expect_handle_prepare_lock_complete(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, handle_prepare_lock_complete()); + } + + void expect_block_writes(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, block_writes(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_unblock_writes(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, unblock_writes()).Times(1); + } + + void expect_verify_lock_ownership(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillRepeatedly(Return(true)); + } + } + + void expect_block_requests(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, block_requests(0)).Times(1); + } + } + + void expect_unblock_requests(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, unblock_requests()).Times(1); + } + } + + void expect_set_flags_request_send( + MockOperationImageCtx &mock_image_ctx, + MockSetFlagsRequest &mock_set_flags_request, int r) { + EXPECT_CALL(mock_set_flags_request, send()) + .WillOnce(FinishRequest(&mock_set_flags_request, r, + &mock_image_ctx)); + } + + void expect_create_journal_request_send( + MockOperationImageCtx &mock_image_ctx, + MockCreateJournalRequest &mock_create_journal_request, int r) { + EXPECT_CALL(mock_create_journal_request, send()) + .WillOnce(FinishRequest(&mock_create_journal_request, r, + &mock_image_ctx)); + } + + void expect_enable_mirror_request_send( + MockOperationImageCtx &mock_image_ctx, + MockEnableMirrorRequest &mock_enable_mirror_request, int r) { + EXPECT_CALL(mock_enable_mirror_request, send()) + .WillOnce(FinishRequest(&mock_enable_mirror_request, r, + &mock_image_ctx)); + } + + void expect_create_object_map_request_send( + MockOperationImageCtx &mock_image_ctx, + MockCreateObjectMapRequest &mock_create_object_map_request, int r) { + EXPECT_CALL(mock_create_object_map_request, send()) + .WillOnce(FinishRequest(&mock_create_object_map_request, r, + &mock_image_ctx)); + } + + void expect_notify_update(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, notify_update(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + +}; + +TEST_F(TestMockOperationEnableFeaturesRequest, All) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + uint64_t features_to_enable = RBD_FEATURES_MUTABLE & features; + + REQUIRE(features_to_enable); + + ensure_features_disabled(ictx, features_to_enable); + + MockOperationImageCtx mock_image_ctx(*ictx); + + MockSetFlagsRequest mock_set_flags_request; + MockCreateJournalRequest mock_create_journal_request; + MockCreateObjectMapRequest mock_create_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_block_requests(mock_image_ctx); + if (features_to_enable & RBD_FEATURE_JOURNALING) { + expect_create_journal_request_send(mock_image_ctx, + mock_create_journal_request, 0); + } + if (features_to_enable & (RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF)) { + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, 0); + } + if (features_to_enable & RBD_FEATURE_OBJECT_MAP) { + expect_create_object_map_request_send(mock_image_ctx, + mock_create_object_map_request, 0); + } + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, features_to_enable); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, ObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled( + ictx, RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockCreateObjectMapRequest mock_create_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + expect_append_op_event(mock_image_ctx, true, 0); + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, 0); + expect_create_object_map_request_send(mock_image_ctx, + mock_create_object_map_request, 0); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_OBJECT_MAP); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, ObjectMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled( + ictx, RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockCreateObjectMapRequest mock_create_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + expect_append_op_event(mock_image_ctx, true, 0); + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, 0); + expect_create_object_map_request_send( + mock_image_ctx, mock_create_object_map_request, -EINVAL); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_OBJECT_MAP); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, SetFlagsError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled( + ictx, RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockCreateObjectMapRequest mock_create_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + expect_append_op_event(mock_image_ctx, true, 0); + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, -EINVAL); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_OBJECT_MAP); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, Mirroring) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled(ictx, RBD_FEATURE_JOURNALING); + + PoolMirrorModeEnabler enabler(m_ioctx); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_verify_lock_ownership(mock_image_ctx); + + MockCreateJournalRequest mock_create_journal_request; + MockEnableMirrorRequest mock_enable_mirror_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_block_requests(mock_image_ctx); + expect_create_journal_request_send(mock_image_ctx, + mock_create_journal_request, 0); + expect_enable_mirror_request_send(mock_image_ctx, + mock_enable_mirror_request, 0); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_JOURNALING); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, JournalingError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled(ictx, RBD_FEATURE_JOURNALING); + + PoolMirrorModeEnabler enabler(m_ioctx); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_verify_lock_ownership(mock_image_ctx); + + MockCreateJournalRequest mock_create_journal_request; + MockEnableMirrorRequest mock_enable_mirror_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_block_requests(mock_image_ctx); + expect_create_journal_request_send(mock_image_ctx, + mock_create_journal_request, -EINVAL); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_JOURNALING); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, MirroringError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled(ictx, RBD_FEATURE_JOURNALING); + + PoolMirrorModeEnabler enabler(m_ioctx); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_verify_lock_ownership(mock_image_ctx); + + MockCreateJournalRequest mock_create_journal_request; + MockEnableMirrorRequest mock_enable_mirror_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_block_requests(mock_image_ctx); + expect_create_journal_request_send(mock_image_ctx, + mock_create_journal_request, 0); + expect_enable_mirror_request_send(mock_image_ctx, + mock_enable_mirror_request, -EINVAL); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_JOURNALING); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_Request.cc b/src/test/librbd/operation/test_mock_Request.cc new file mode 100644 index 00000000..df0e38d1 --- /dev/null +++ b/src/test/librbd/operation/test_mock_Request.cc @@ -0,0 +1,175 @@ +// -*- 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/MockJournal.h" +#include "librbd/AsyncRequest.h" +#include "librbd/operation/Request.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +template <> +struct AsyncRequest<librbd::MockTestImageCtx> { + librbd::MockTestImageCtx &m_image_ctx; + Context *m_on_finish; + + AsyncRequest(librbd::MockTestImageCtx &image_ctx, Context *on_finish) + : m_image_ctx(image_ctx), m_on_finish(on_finish) { + } + virtual ~AsyncRequest() { + } + + virtual void finish(int r) { + m_on_finish->complete(r); + } + virtual void finish_and_destroy(int r) { + finish(r); + delete this; + } +}; + +} // namespace librbd + +#include "librbd/operation/Request.cc" + +namespace librbd { +namespace journal { + +std::ostream& operator<<(std::ostream& os, const Event&) { + return os; +} + +} // namespace journal + +namespace operation { + +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; + +struct MockRequest : public Request<librbd::MockTestImageCtx> { + MockRequest(librbd::MockTestImageCtx &image_ctx, Context *on_finish, + uint64_t journal_op_tid) + : Request<librbd::MockTestImageCtx>(image_ctx, on_finish, journal_op_tid) { + } + + void complete(int r) { + finish_and_destroy(r); + } + + void send_op_impl(int r) { + bool appending = append_op_event< + MockRequest, &MockRequest::handle_send>(this); + if (!appending) { + complete(r); + } + } + MOCK_METHOD1(should_complete, bool(int)); + MOCK_METHOD0(send_op, void()); + MOCK_METHOD1(handle_send, Context*(int*)); + MOCK_CONST_METHOD0(can_affect_io, bool()); + MOCK_CONST_METHOD1(create_event, journal::Event(uint64_t)); +}; + +struct TestMockOperationRequest : public TestMockFixture { + void expect_can_affect_io(MockRequest &mock_request, bool can_affect) { + EXPECT_CALL(mock_request, can_affect_io()) + .WillOnce(Return(can_affect)); + } + + void expect_is_journal_replaying(MockJournal &mock_journal, bool replaying) { + EXPECT_CALL(mock_journal, is_journal_replaying()) + .WillOnce(Return(replaying)); + } + + void expect_is_journal_appending(MockJournal &mock_journal, bool appending) { + EXPECT_CALL(mock_journal, is_journal_appending()) + .WillOnce(Return(appending)); + } + + void expect_send_op(MockRequest &mock_request, int r) { + EXPECT_CALL(mock_request, send_op()) + .WillOnce(Invoke([&mock_request, r]() { + mock_request.complete(r); + })); + } + + void expect_send_op_affects_io(MockImageCtx &mock_image_ctx, + MockRequest &mock_request, int r) { + EXPECT_CALL(mock_request, send_op()) + .WillOnce(Invoke([&mock_image_ctx, &mock_request, r]() { + mock_image_ctx.image_ctx->op_work_queue->queue( + new FunctionContext([&mock_request, r](int _) { + mock_request.send_op_impl(r); + }), 0); + })); + } + +}; + +TEST_F(TestMockOperationRequest, SendJournalDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + C_SaferCond ctx; + MockRequest *mock_request = new MockRequest(mock_image_ctx, &ctx, 0); + + InSequence seq; + expect_can_affect_io(*mock_request, false); + expect_is_journal_appending(mock_journal, false); + expect_send_op(*mock_request, 0); + + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_request->send(); + } + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockOperationRequest, SendAffectsIOJournalDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + C_SaferCond ctx; + MockRequest *mock_request = new MockRequest(mock_image_ctx, &ctx, 0); + + InSequence seq; + expect_can_affect_io(*mock_request, true); + expect_send_op_affects_io(mock_image_ctx, *mock_request, 0); + expect_can_affect_io(*mock_request, true); + expect_is_journal_replaying(mock_journal, false); + expect_is_journal_appending(mock_journal, false); + + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + mock_request->send(); + } + + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_ResizeRequest.cc b/src/test/librbd/operation/test_mock_ResizeRequest.cc new file mode 100644 index 00000000..0f959230 --- /dev/null +++ b/src/test/librbd/operation/test_mock_ResizeRequest.cc @@ -0,0 +1,461 @@ +// -*- 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/io/MockObjectDispatch.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/io/ImageDispatchSpec.h" +#include "librbd/operation/ResizeRequest.h" +#include "librbd/operation/TrimRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { + +namespace util { + +inline ImageCtx* get_image_ctx(MockImageCtx* image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util + +namespace io { + +template <> +struct ImageDispatchSpec<MockImageCtx> { + static ImageDispatchSpec* s_instance; + AioCompletion *aio_comp = nullptr; + + static ImageDispatchSpec* create_flush_request( + MockImageCtx &image_ctx, AioCompletion *aio_comp, + FlushSource flush_source, const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_comp = aio_comp; + return s_instance; + } + + MOCK_CONST_METHOD0(send, void()); + + ImageDispatchSpec() { + s_instance = this; + } +}; + +ImageDispatchSpec<MockImageCtx>* ImageDispatchSpec<MockImageCtx>::s_instance = nullptr; + +} // namespace io + +namespace operation { + +template <> +class TrimRequest<MockImageCtx> { +public: + static TrimRequest *s_instance; + static TrimRequest *create(MockImageCtx &image_ctx, Context *on_finish, + uint64_t original_size, uint64_t new_size, + ProgressContext &prog_ctx) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + TrimRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +TrimRequest<MockImageCtx> *TrimRequest<MockImageCtx>::s_instance = nullptr; + +} // namespace operation +} // namespace librbd + +// template definitions +#include "librbd/operation/ResizeRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockOperationResizeRequest : public TestMockFixture { +public: + typedef ResizeRequest<MockImageCtx> MockResizeRequest; + typedef TrimRequest<MockImageCtx> MockTrimRequest; + typedef io::ImageDispatchSpec<MockImageCtx> MockIoImageDispatchSpec; + + void expect_block_writes(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, block_writes(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_unblock_writes(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, unblock_writes()) + .Times(1); + } + + void expect_is_lock_owner(MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillOnce(Return(true)); + } + } + + void expect_grow_object_map(MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.object_map != nullptr) { + expect_is_lock_owner(mock_image_ctx); + EXPECT_CALL(*mock_image_ctx.object_map, aio_resize(_, _, _)) + .WillOnce(WithArg<2>(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_shrink_object_map(MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.object_map != nullptr) { + expect_is_lock_owner(mock_image_ctx); + EXPECT_CALL(*mock_image_ctx.object_map, aio_resize(_, _, _)) + .WillOnce(WithArg<2>(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_update_header(MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.old_format) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + write(mock_image_ctx.header_oid, _, _, _, _)) + .WillOnce(Return(r)); + } else { + expect_is_lock_owner(mock_image_ctx); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("set_size"), _, _, _)) + .WillOnce(Return(r)); + } + } + + void expect_trim(MockImageCtx &mock_image_ctx, + MockTrimRequest &mock_trim_request, int r) { + EXPECT_CALL(mock_trim_request, send()) + .WillOnce(FinishRequest(&mock_trim_request, r, &mock_image_ctx)); + } + + void expect_flush_cache(MockImageCtx &mock_image_ctx, + MockIoImageDispatchSpec& mock_io_image_dispatch_spec, + int r) { + EXPECT_CALL(mock_io_image_dispatch_spec, send()) + .WillOnce(Invoke([&mock_image_ctx, &mock_io_image_dispatch_spec, r]() { + auto aio_comp = mock_io_image_dispatch_spec.s_instance->aio_comp; + auto ctx = new FunctionContext([aio_comp](int r) { + aio_comp->get(); + aio_comp->fail(r); + }); + mock_image_ctx.image_ctx->op_work_queue->queue(ctx, r); + })); + } + + void expect_invalidate_cache(MockImageCtx &mock_image_ctx, + int r) { + EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, invalidate_cache(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + expect_op_work_queue(mock_image_ctx); + } + + void expect_resize_object_map(MockImageCtx &mock_image_ctx, + uint64_t new_size) { + EXPECT_CALL(*mock_image_ctx.object_map, aio_resize(new_size, _, _)) + .WillOnce(WithArg<2>(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue))); + } + + int when_resize(MockImageCtx &mock_image_ctx, uint64_t new_size, + bool allow_shrink, uint64_t journal_op_tid, + bool disable_journal) { + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockResizeRequest *req = new MockResizeRequest( + mock_image_ctx, &cond_ctx, new_size, allow_shrink, prog_ctx, + journal_op_tid, disable_journal); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + return cond_ctx.wait(); + } +}; + +TEST_F(TestMockOperationResizeRequest, NoOpSuccess) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, 0); + ASSERT_EQ(0, when_resize(mock_image_ctx, ictx->size, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, GrowSuccess) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_grow_object_map(mock_image_ctx); + expect_update_header(mock_image_ctx, 0); + expect_unblock_writes(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, 0); + ASSERT_EQ(0, when_resize(mock_image_ctx, ictx->size * 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, ShrinkSuccess) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + + MockTrimRequest mock_trim_request; + auto mock_io_image_dispatch_spec = new MockIoImageDispatchSpec(); + expect_flush_cache(mock_image_ctx, *mock_io_image_dispatch_spec, 0); + expect_invalidate_cache(mock_image_ctx, 0); + expect_trim(mock_image_ctx, mock_trim_request, 0); + expect_block_writes(mock_image_ctx, 0); + expect_update_header(mock_image_ctx, 0); + expect_shrink_object_map(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, 0); + ASSERT_EQ(0, when_resize(mock_image_ctx, ictx->size / 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, ShrinkError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size / 2, false, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, PreBlockWritesError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, TrimError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + + MockTrimRequest mock_trim_request; + auto mock_io_image_dispatch_spec = new MockIoImageDispatchSpec(); + expect_flush_cache(mock_image_ctx, *mock_io_image_dispatch_spec, 0); + expect_invalidate_cache(mock_image_ctx, -EBUSY); + expect_trim(mock_image_ctx, mock_trim_request, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size / 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, FlushCacheError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + REQUIRE(ictx->cache); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + + MockTrimRequest mock_trim_request; + auto mock_io_image_dispatch_spec = new MockIoImageDispatchSpec(); + expect_flush_cache(mock_image_ctx, *mock_io_image_dispatch_spec, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size / 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, InvalidateCacheError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + REQUIRE(ictx->cache); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + + MockTrimRequest mock_trim_request; + auto mock_io_image_dispatch_spec = new MockIoImageDispatchSpec(); + expect_flush_cache(mock_image_ctx, *mock_io_image_dispatch_spec, 0); + expect_invalidate_cache(mock_image_ctx, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size / 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, PostBlockWritesError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + + MockTrimRequest mock_trim_request; + auto mock_io_image_dispatch_spec = new MockIoImageDispatchSpec(); + expect_flush_cache(mock_image_ctx, *mock_io_image_dispatch_spec, 0); + expect_invalidate_cache(mock_image_ctx, 0); + expect_trim(mock_image_ctx, mock_trim_request, 0); + expect_block_writes(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, -EINVAL); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size / 2, true, 0, + false)); +} + +TEST_F(TestMockOperationResizeRequest, UpdateHeaderError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_grow_object_map(mock_image_ctx); + expect_update_header(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, -EINVAL); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size * 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, JournalAppendError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, JournalDisabled) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(0, when_resize(mock_image_ctx, ictx->size, true, 0, true)); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc b/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc new file mode 100644 index 00000000..64d995d4 --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc @@ -0,0 +1,313 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/operation/SnapshotCreateRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +// template definitions +#include "librbd/operation/SnapshotCreateRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockOperationSnapshotCreateRequest : public TestMockFixture { +public: + typedef SnapshotCreateRequest<MockImageCtx> MockSnapshotCreateRequest; + + void expect_block_writes(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, block_writes(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_verify_lock_ownership(MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillRepeatedly(Return(true)); + } + } + + void expect_allocate_snap_id(MockImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + selfmanaged_snap_create(_)); + if (r < 0 && r != -ESTALE) { + expect.WillOnce(Return(r)); + } else { + expect.Times(r < 0 ? 2 : 1).WillRepeatedly(DoDefault()); + } + } + + void expect_release_snap_id(MockImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + selfmanaged_snap_remove(_)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_snap_create(MockImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq(mock_image_ctx.old_format ? "snap_add" : + "snapshot_add"), + _, _, _)); + if (r == -ESTALE) { + expect.WillOnce(Return(r)).WillOnce(DoDefault()); + } else if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_object_map_snap_create(MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, snapshot_add(_, _)) + .WillOnce(WithArg<1>(CompleteContext( + 0, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_update_snap_context(MockImageCtx &mock_image_ctx) { + // state machine checks to ensure a refresh hasn't already added the snap + EXPECT_CALL(mock_image_ctx, get_snap_info(_)) + .WillOnce(Return(static_cast<const librbd::SnapInfo*>(NULL))); + EXPECT_CALL(mock_image_ctx, add_snap(_, "snap1", _, _, _, _, _, _)); + } + + void expect_unblock_writes(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, unblock_writes()) + .Times(1); + } + +}; + +TEST_F(TestMockOperationSnapshotCreateRequest, Success) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, 0); + expect_snap_create(mock_image_ctx, 0); + if (!mock_image_ctx.old_format) { + expect_object_map_snap_create(mock_image_ctx); + expect_update_snap_context(mock_image_ctx); + } + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, false); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, AllocateSnapIdError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, false); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, CreateSnapStale) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, -ESTALE); + expect_snap_create(mock_image_ctx, -ESTALE); + if (!mock_image_ctx.old_format) { + expect_object_map_snap_create(mock_image_ctx); + expect_update_snap_context(mock_image_ctx); + } + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, false); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, CreateSnapError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, 0); + expect_snap_create(mock_image_ctx, -EINVAL); + expect_release_snap_id(mock_image_ctx, 0); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, false); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, ReleaseSnapIdError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, 0); + expect_snap_create(mock_image_ctx, -EINVAL); + expect_release_snap_id(mock_image_ctx, -ESTALE); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, false); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, SkipObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, 0); + expect_snap_create(mock_image_ctx, 0); + expect_update_snap_context(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, true); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc b/src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc new file mode 100644 index 00000000..c583f03a --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc @@ -0,0 +1,192 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/operation/SnapshotProtectRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +// template definitions +#include "librbd/operation/SnapshotProtectRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockOperationSnapshotProtectRequest : public TestMockFixture { +public: + typedef SnapshotProtectRequest<MockImageCtx> MockSnapshotProtectRequest; + + void expect_get_snap_id(MockImageCtx &mock_image_ctx, uint64_t snap_id) { + EXPECT_CALL(mock_image_ctx, get_snap_id(_, _)) + .WillOnce(Return(snap_id)); + } + + void expect_is_snap_protected(MockImageCtx &mock_image_ctx, bool is_protected, + int r) { + auto &expect = EXPECT_CALL(mock_image_ctx, is_snap_protected(_, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoAll(SetArgPointee<1>(is_protected), Return(0))); + } + } + + void expect_set_protection_status(MockImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("set_protection_status"), _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } +}; + +TEST_F(TestMockOperationSnapshotProtectRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_protected(mock_image_ctx, false, 0); + expect_set_protection_status(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotProtectRequest, GetSnapIdMissing) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, CEPH_NOSNAP); + + C_SaferCond cond_ctx; + MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotProtectRequest, IsSnapProtectedError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_protected(mock_image_ctx, false, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotProtectRequest, SnapAlreadyProtected) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_protected(mock_image_ctx, true, 0); + + C_SaferCond cond_ctx; + MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EBUSY, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotProtectRequest, SetProtectionStateError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_protected(mock_image_ctx, false, 0); + expect_set_protection_status(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc b/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc new file mode 100644 index 00000000..53c0ca1b --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc @@ -0,0 +1,833 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/Operations.h" +#include "librbd/image/DetachChildRequest.h" +#include "librbd/operation/SnapshotRemoveRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +// template definitions +#include "librbd/operation/SnapshotRemoveRequest.cc" + +namespace librbd { +namespace image { + +template <> +class DetachChildRequest<MockImageCtx> { +public: + static DetachChildRequest *s_instance; + static DetachChildRequest *create(MockImageCtx &image_ctx, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + DetachChildRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +DetachChildRequest<MockImageCtx> *DetachChildRequest<MockImageCtx>::s_instance; + +} // namespace image + +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockOperationSnapshotRemoveRequest : public TestMockFixture { +public: + typedef SnapshotRemoveRequest<MockImageCtx> MockSnapshotRemoveRequest; + typedef image::DetachChildRequest<MockImageCtx> MockDetachChildRequest; + + int create_snapshot(const char *snap_name) { + librbd::ImageCtx *ictx; + int r = open_image(m_image_name, &ictx); + if (r < 0) { + return r; + } + + r = snap_create(*ictx, snap_name); + if (r < 0) { + return r; + } + + r = snap_protect(*ictx, snap_name); + if (r < 0) { + return r; + } + close_image(ictx); + return 0; + } + + void expect_snapshot_trash_add(MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.old_format) { + return; + } + + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("snapshot_trash_add"), + _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_snapshot_get(MockImageCtx &mock_image_ctx, + const cls::rbd::SnapshotInfo& snap_info, int r) { + if (mock_image_ctx.old_format) { + return; + } + + using ceph::encode; + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("snapshot_get"), _, _, _)) + .WillOnce(WithArg<5>(Invoke([snap_info, r](bufferlist* bl) { + encode(snap_info, *bl); + return r; + }))); + } + + void expect_children_list(MockImageCtx &mock_image_ctx, + const cls::rbd::ChildImageSpecs& child_images, int r) { + if (mock_image_ctx.old_format) { + return; + } + + using ceph::encode; + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("children_list"), _, _, _)) + .WillOnce(WithArg<5>(Invoke([child_images, r](bufferlist* bl) { + encode(child_images, *bl); + return r; + }))); + } + + void expect_detach_stale_child(MockImageCtx &mock_image_ctx, int r) { + auto& parent_spec = mock_image_ctx.parent_md.spec; + + bufferlist bl; + encode(parent_spec.snap_id, bl); + encode(cls::rbd::ChildImageSpec{mock_image_ctx.md_ctx.get_id(), "", + mock_image_ctx.id}, bl); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(util::header_name(parent_spec.image_id), + _, StrEq("rbd"), StrEq("child_detach"), ContentsEqual(bl), + _, _)) + .WillOnce(Return(r)); + } + + void expect_object_map_snap_remove(MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, snapshot_remove(_, _)) + .WillOnce(WithArg<1>(CompleteContext( + r, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_get_parent_spec(MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.old_format) { + return; + } + + auto &expect = EXPECT_CALL(mock_image_ctx, get_parent_spec(_, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + auto &parent_spec = mock_image_ctx.snap_info.rbegin()->second.parent.spec; + expect.WillOnce(DoAll(SetArgPointee<1>(parent_spec), + Return(0))); + } + } + + void expect_detach_child(MockImageCtx &mock_image_ctx, + MockDetachChildRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(FinishRequest(&mock_request, r, &mock_image_ctx)); + } + + void expect_snap_remove(MockImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq(mock_image_ctx.old_format ? "snap_remove" : + "snapshot_remove"), + _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_rm_snap(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, rm_snap(_, _, _)).Times(1); + } + + void expect_release_snap_id(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + selfmanaged_snap_remove(_)) + .WillOnce(DoDefault()); + } + +}; + +TEST_F(TestMockOperationSnapshotRemoveRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, SuccessCloneParent) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 1}, 0); + + const cls::rbd::ChildImageSpecs child_images; + expect_children_list(mock_image_ctx, child_images, 0); + expect_get_parent_spec(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, SuccessTrash) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, + {cls::rbd::TrashSnapshotNamespace{ + cls::rbd::SNAPSHOT_NAMESPACE_TYPE_USER, "snap1"}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, FlattenedCloneRemovesChild) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + REQUIRE(!is_feature_enabled(RBD_FEATURE_DEEP_FLATTEN)) + + ASSERT_EQ(0, create_snapshot("snap1")); + + int order = 22; + uint64_t features; + ASSERT_TRUE(::get_features(&features)); + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + + librbd::NoOpProgressContext prog_ctx; + ASSERT_EQ(0, flatten(*ictx, prog_ctx)); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + + MockDetachChildRequest mock_detach_child_request; + expect_detach_child(mock_image_ctx, mock_detach_child_request, -ENOENT); + + expect_object_map_snap_remove(mock_image_ctx, 0); + + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, TrashCloneParent) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, ictx->operations->snap_create( + {cls::rbd::TrashSnapshotNamespace{}}, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 1}, 0); + const cls::rbd::ChildImageSpecs child_images; + expect_children_list(mock_image_ctx, child_images, 0); + expect_get_parent_spec(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::TrashSnapshotNamespace{}, "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EBUSY, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, SnapshotTrashAddNotSupported) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, -EOPNOTSUPP); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_get_parent_spec(mock_image_ctx, 0); + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, SnapshotTrashAddError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_trash_add(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, SnapshotGetError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, -EOPNOTSUPP); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EOPNOTSUPP, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, ObjectMapSnapRemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + + expect_object_map_snap_remove(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, RemoveChildParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, -ENOENT); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, RemoveChildError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snapshot("snap1")); + + int order = 22; + uint64_t features; + ASSERT_TRUE(::get_features(&features)); + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + if (ictx->test_features(RBD_FEATURE_DEEP_FLATTEN)) { + std::cout << "SKIPPING" << std::endl; + return SUCCEED(); + } + + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + + librbd::NoOpProgressContext prog_ctx; + ASSERT_EQ(0, flatten(*ictx, prog_ctx)); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_get_parent_spec(mock_image_ctx, 0); + + MockDetachChildRequest mock_detach_child_request; + expect_detach_child(mock_image_ctx, mock_detach_child_request, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, RemoveSnapError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, -ENOENT); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, MissingSnap) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = 456; + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, ListChildrenError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 1}, 0); + const cls::rbd::ChildImageSpecs child_images; + expect_children_list(mock_image_ctx, child_images, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, DetachStaleChildError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snapshot("snap1")); + + int order = 22; + uint64_t features; + ASSERT_TRUE(::get_features(&features)); + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 1}, 0); + const cls::rbd::ChildImageSpecs child_images; + expect_children_list(mock_image_ctx, child_images, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_SnapshotRollbackRequest.cc b/src/test/librbd/operation/test_mock_SnapshotRollbackRequest.cc new file mode 100644 index 00000000..1727180e --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotRollbackRequest.cc @@ -0,0 +1,367 @@ +// -*- 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/io/MockObjectDispatch.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "include/stringify.h" +#include "common/bit_vector.hpp" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/operation/SnapshotRollbackRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { + +namespace { + +struct MockOperationImageCtx : public MockImageCtx { + MockOperationImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace operation { + +template <> +struct ResizeRequest<MockOperationImageCtx> { + static ResizeRequest *s_instance; + Context *on_finish = nullptr; + + static ResizeRequest* create(MockOperationImageCtx &image_ctx, Context *on_finish, + uint64_t new_size, bool allow_shrink, + ProgressContext &prog_ctx, uint64_t journal_op_tid, + bool disable_journal) { + ceph_assert(s_instance != nullptr); + ceph_assert(journal_op_tid == 0); + ceph_assert(disable_journal); + s_instance->on_finish = on_finish; + return s_instance; + } + + ResizeRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +ResizeRequest<MockOperationImageCtx> *ResizeRequest<MockOperationImageCtx>::s_instance = nullptr; + +} // namespace operation + +template <> +struct AsyncRequest<MockOperationImageCtx> : public AsyncRequest<MockImageCtx> { + MockOperationImageCtx &m_image_ctx; + + AsyncRequest(MockOperationImageCtx &image_ctx, Context *on_finish) + : AsyncRequest<MockImageCtx>(image_ctx, on_finish), m_image_ctx(image_ctx) { + } +}; + +} // namespace librbd + +// template definitions +#include "librbd/AsyncRequest.cc" +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/operation/Request.cc" +#include "librbd/operation/SnapshotRollbackRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::WithArg; + +class TestMockOperationSnapshotRollbackRequest : public TestMockFixture { +public: + typedef SnapshotRollbackRequest<MockOperationImageCtx> MockSnapshotRollbackRequest; + typedef ResizeRequest<MockOperationImageCtx> MockResizeRequest; + + void expect_block_writes(MockOperationImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, block_writes(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_unblock_writes(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, unblock_writes()) + .Times(1); + } + + void expect_get_image_size(MockOperationImageCtx &mock_image_ctx, + uint64_t size) { + EXPECT_CALL(mock_image_ctx, get_image_size(CEPH_NOSNAP)) + .WillOnce(Return(size)); + } + + void expect_resize(MockOperationImageCtx &mock_image_ctx, + MockResizeRequest &mock_resize_request, int r) { + expect_get_image_size(mock_image_ctx, 123); + EXPECT_CALL(mock_resize_request, send()) + .WillOnce(FinishRequest(&mock_resize_request, r, + &mock_image_ctx)); + } + + void expect_get_flags(MockOperationImageCtx &mock_image_ctx, + uint64_t snap_id, int r) { + EXPECT_CALL(mock_image_ctx, get_flags(snap_id, _)) + .WillOnce(Return(r)); + } + + void expect_object_may_exist(MockOperationImageCtx &mock_image_ctx, + uint64_t object_no, bool exists) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, object_may_exist(object_no)) + .WillOnce(Return(exists)); + } + } + + void expect_get_snap_object_map(MockOperationImageCtx &mock_image_ctx, + MockObjectMap *mock_object_map, uint64_t snap_id) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(mock_image_ctx, create_object_map(snap_id)) + .WillOnce(Return(mock_object_map)); + EXPECT_CALL(*mock_object_map, open(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + } + + void expect_rollback_object_map(MockOperationImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(mock_object_map, rollback(_, _)) + .WillOnce(WithArg<1>(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_get_object_name(MockOperationImageCtx &mock_image_ctx, + uint64_t object_num) { + EXPECT_CALL(mock_image_ctx, get_object_name(object_num)) + .WillOnce(Return("object-name-" + stringify(object_num))); + } + + void expect_get_current_size(MockOperationImageCtx &mock_image_ctx, uint64_t size) { + EXPECT_CALL(mock_image_ctx, get_current_size()) + .WillOnce(Return(size)); + } + + void expect_rollback_snap_id(MockOperationImageCtx &mock_image_ctx, + const std::string &oid, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + selfmanaged_snap_rollback(oid, _)) + .WillOnce(Return(r)); + } + + void expect_rollback(MockOperationImageCtx &mock_image_ctx, int r) { + expect_get_current_size(mock_image_ctx, 1); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_object_name(mock_image_ctx, 0); + expect_rollback_snap_id(mock_image_ctx, "object-name-0", r); + } + + void expect_create_object_map(MockOperationImageCtx &mock_image_ctx, + MockObjectMap *mock_object_map) { + EXPECT_CALL(mock_image_ctx, create_object_map(_)) + .WillOnce(Return(mock_object_map)); + } + + void expect_open_object_map(MockOperationImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map) { + EXPECT_CALL(mock_object_map, open(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_refresh_object_map(MockOperationImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map) { + if (mock_image_ctx.object_map != nullptr) { + expect_create_object_map(mock_image_ctx, &mock_object_map); + expect_open_object_map(mock_image_ctx, mock_object_map); + } + } + + void expect_invalidate_cache(MockOperationImageCtx &mock_image_ctx, + int r) { + EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, invalidate_cache(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + int when_snap_rollback(MockOperationImageCtx &mock_image_ctx, + const std::string &snap_name, + uint64_t snap_id, uint64_t snap_size) { + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockSnapshotRollbackRequest *req = new MockSnapshotRollbackRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), snap_name, + snap_id, snap_size, prog_ctx); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + return cond_ctx.wait(); + } +}; + +TEST_F(TestMockOperationSnapshotRollbackRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap *mock_object_map = new MockObjectMap(); + MockObjectMap *mock_snap_object_map = new MockObjectMap(); + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + *mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + MockResizeRequest mock_resize_request; + expect_append_op_event(mock_image_ctx, false, 0); + expect_block_writes(mock_image_ctx, 0); + expect_resize(mock_image_ctx, mock_resize_request, 0); + expect_get_flags(mock_image_ctx, 123, 0); + expect_get_snap_object_map(mock_image_ctx, mock_snap_object_map, 123); + expect_rollback_object_map(mock_image_ctx, *mock_object_map); + expect_rollback(mock_image_ctx, 0); + expect_refresh_object_map(mock_image_ctx, *mock_object_map); + expect_invalidate_cache(mock_image_ctx, 0); + expect_commit_op_event(mock_image_ctx, 0); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(0, when_snap_rollback(mock_image_ctx, "snap", 123, 0)); +} + +TEST_F(TestMockOperationSnapshotRollbackRequest, BlockWritesError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_append_op_event(mock_image_ctx, false, 0); + expect_block_writes(mock_image_ctx, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_snap_rollback(mock_image_ctx, "snap", 123, 0)); +} + +TEST_F(TestMockOperationSnapshotRollbackRequest, SkipResize) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap *mock_object_map = new MockObjectMap(); + MockObjectMap *mock_snap_object_map = new MockObjectMap(); + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + *mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_append_op_event(mock_image_ctx, false, 0); + expect_block_writes(mock_image_ctx, 0); + expect_get_image_size(mock_image_ctx, 345); + expect_get_flags(mock_image_ctx, 123, 0); + expect_get_snap_object_map(mock_image_ctx, mock_snap_object_map, 123); + expect_rollback_object_map(mock_image_ctx, *mock_object_map); + expect_rollback(mock_image_ctx, 0); + expect_refresh_object_map(mock_image_ctx, *mock_object_map); + expect_invalidate_cache(mock_image_ctx, 0); + expect_commit_op_event(mock_image_ctx, 0); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(0, when_snap_rollback(mock_image_ctx, "snap", 123, 345)); +} + +TEST_F(TestMockOperationSnapshotRollbackRequest, ResizeError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + MockResizeRequest mock_resize_request; + expect_append_op_event(mock_image_ctx, false, 0); + expect_block_writes(mock_image_ctx, 0); + expect_resize(mock_image_ctx, mock_resize_request, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_snap_rollback(mock_image_ctx, "snap", 123, 0)); +} + +TEST_F(TestMockOperationSnapshotRollbackRequest, RollbackObjectsError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + MockObjectMap *mock_snap_object_map = new MockObjectMap(); + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + MockResizeRequest mock_resize_request; + expect_append_op_event(mock_image_ctx, false, 0); + expect_block_writes(mock_image_ctx, 0); + expect_resize(mock_image_ctx, mock_resize_request, 0); + expect_get_flags(mock_image_ctx, 123, 0); + expect_get_snap_object_map(mock_image_ctx, mock_snap_object_map, 123); + expect_rollback_object_map(mock_image_ctx, mock_object_map); + expect_rollback(mock_image_ctx, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_snap_rollback(mock_image_ctx, "snap", 123, 0)); +} + +TEST_F(TestMockOperationSnapshotRollbackRequest, InvalidateCacheError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + REQUIRE(ictx->cache); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap *mock_object_map = new MockObjectMap(); + MockObjectMap *mock_snap_object_map = new MockObjectMap(); + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + *mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + MockResizeRequest mock_resize_request; + expect_append_op_event(mock_image_ctx, false, 0); + expect_block_writes(mock_image_ctx, 0); + expect_resize(mock_image_ctx, mock_resize_request, 0); + expect_get_flags(mock_image_ctx, 123, 0); + expect_get_snap_object_map(mock_image_ctx, mock_snap_object_map, 123); + expect_rollback_object_map(mock_image_ctx, *mock_object_map); + expect_rollback(mock_image_ctx, 0); + expect_refresh_object_map(mock_image_ctx, *mock_object_map); + expect_invalidate_cache(mock_image_ctx, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_snap_rollback(mock_image_ctx, "snap", 123, 0)); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc b/src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc new file mode 100644 index 00000000..da344dcb --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc @@ -0,0 +1,277 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "include/rados/librados.hpp" +#include "common/bit_vector.hpp" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/operation/SnapshotUnprotectRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +// template definitions +#include "librbd/operation/SnapshotUnprotectRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::SetArgReferee; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockOperationSnapshotUnprotectRequest : public TestMockFixture { +public: + typedef SnapshotUnprotectRequest<MockImageCtx> MockSnapshotUnprotectRequest; + + void expect_get_snap_id(MockImageCtx &mock_image_ctx, uint64_t snap_id) { + EXPECT_CALL(mock_image_ctx, get_snap_id(_, _)) + .WillOnce(Return(snap_id)); + } + + void expect_is_snap_unprotected(MockImageCtx &mock_image_ctx, + bool is_unprotected, int r) { + auto &expect = EXPECT_CALL(mock_image_ctx, is_snap_unprotected(_, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoAll(SetArgPointee<1>(is_unprotected), Return(0))); + } + } + + void expect_set_protection_status(MockImageCtx &mock_image_ctx, + uint64_t snap_id, uint8_t status, int r) { + bufferlist bl; + encode(snap_id, bl); + encode(status, bl); + + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("set_protection_status"), ContentsEqual(bl), + _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + size_t expect_create_pool_io_contexts(MockImageCtx &mock_image_ctx) { + librados::MockTestMemIoCtxImpl &io_ctx_impl = + get_mock_io_ctx(mock_image_ctx.md_ctx); + librados::MockTestMemRadosClient *rados_client = + io_ctx_impl.get_mock_rados_client(); + + std::list<std::pair<int64_t, std::string> > pools; + int r = rados_client->pool_list(pools); + if (r < 0) { + ADD_FAILURE() << "failed to list pools"; + return 0; + } + + EXPECT_CALL(*rados_client, create_ioctx(_, _)) + .Times(pools.size()).WillRepeatedly(DoAll( + GetReference(&io_ctx_impl), Return(&io_ctx_impl))); + return pools.size(); + } + + void expect_get_children(MockImageCtx &mock_image_ctx, size_t pools, int r) { + bufferlist bl; + std::set<std::string> children; + encode(children, bl); + + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_CHILDREN, _, StrEq("rbd"), StrEq("get_children"), _, + _, _)); + if (r < 0) { + expect.WillRepeatedly(Return(r)); + } else { + expect.Times(pools).WillRepeatedly(DoAll( + SetArgPointee<5>(bl), Return(0))); + } + } +}; + +TEST_F(TestMockOperationSnapshotUnprotectRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_get_snap_id(mock_image_ctx, snap_id); + expect_is_snap_unprotected(mock_image_ctx, false, 0); + expect_set_protection_status(mock_image_ctx, snap_id, + RBD_PROTECTION_STATUS_UNPROTECTING, 0); + size_t pools = expect_create_pool_io_contexts(mock_image_ctx); + expect_get_children(mock_image_ctx, pools, -ENOENT); + expect_set_protection_status(mock_image_ctx, snap_id, + RBD_PROTECTION_STATUS_UNPROTECTED, 0); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotUnprotectRequest, GetSnapIdMissing) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, CEPH_NOSNAP); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotUnprotectRequest, IsSnapUnprotectedError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_unprotected(mock_image_ctx, false, -EBADMSG); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EBADMSG, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotUnprotectRequest, SnapAlreadyUnprotected) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_unprotected(mock_image_ctx, true, 0); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotUnprotectRequest, SetProtectionStatusError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_get_snap_id(mock_image_ctx, snap_id); + expect_is_snap_unprotected(mock_image_ctx, false, 0); + expect_set_protection_status(mock_image_ctx, snap_id, + RBD_PROTECTION_STATUS_UNPROTECTING, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotUnprotectRequest, ChildrenExist) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_get_snap_id(mock_image_ctx, snap_id); + expect_is_snap_unprotected(mock_image_ctx, false, 0); + expect_set_protection_status(mock_image_ctx, snap_id, + RBD_PROTECTION_STATUS_UNPROTECTING, 0); + size_t pools = expect_create_pool_io_contexts(mock_image_ctx); + expect_get_children(mock_image_ctx, pools, 0); + expect_set_protection_status(mock_image_ctx, snap_id, + RBD_PROTECTION_STATUS_PROTECTED, 0); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EBUSY, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_TrimRequest.cc b/src/test/librbd/operation/test_mock_TrimRequest.cc new file mode 100644 index 00000000..39b54f97 --- /dev/null +++ b/src/test/librbd/operation/test_mock_TrimRequest.cc @@ -0,0 +1,474 @@ +// -*- 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/io/MockObjectDispatch.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/AsyncRequest.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/Utils.h" +#include "librbd/operation/TrimRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <boost/variant.hpp> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +template<> +struct AsyncRequest<librbd::MockTestImageCtx> { + librbd::MockTestImageCtx& m_image_ctx; + Context *on_finish; + + AsyncRequest(librbd::MockTestImageCtx& image_ctx, Context* on_finish) + : m_image_ctx(image_ctx), on_finish(on_finish) { + } + virtual ~AsyncRequest() { + } + + Context* create_callback_context() { + return util::create_context_callback(this); + } + + Context* create_async_callback_context() { + return util::create_context_callback<AsyncRequest, + &AsyncRequest::async_complete>(this); + } + + void complete(int r) { + if (should_complete(r)) { + async_complete(r); + } + } + + void async_complete(int r) { + on_finish->complete(r); + delete this; + } + + bool is_canceled() const { + return false; + } + + virtual void send() = 0; + virtual bool should_complete(int r) = 0; +}; + +namespace io { + +struct DiscardVisitor + : public boost::static_visitor<ObjectDispatchSpec::DiscardRequest*> { + ObjectDispatchSpec::DiscardRequest* + operator()(ObjectDispatchSpec::DiscardRequest& discard) const { + return &discard; + } + + template <typename T> + ObjectDispatchSpec::DiscardRequest* + operator()(T& t) const { + return nullptr; + } +}; + +} // namespace io +} // namespace librbd + +// template definitions +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/operation/TrimRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockOperationTrimRequest : public TestMockFixture { +public: + typedef TrimRequest<MockTestImageCtx> MockTrimRequest; + + int create_snapshot(const char *snap_name) { + librbd::ImageCtx *ictx; + int r = open_image(m_image_name, &ictx); + if (r < 0) { + return r; + } + + r = snap_create(*ictx, snap_name); + if (r < 0) { + return r; + } + + r = snap_protect(*ictx, snap_name); + if (r < 0) { + return r; + } + close_image(ictx); + return 0; + } + + void expect_is_lock_owner(MockTestImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillRepeatedly(Return(true)); + } + } + + void expect_object_map_update(MockTestImageCtx &mock_image_ctx, + uint64_t start_object, uint64_t end_object, + uint8_t state, uint8_t current_state, + bool updated, int ret_val) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, + aio_update(CEPH_NOSNAP, start_object, end_object, state, + boost::optional<uint8_t>(current_state), _, false, _)) + .WillOnce(WithArg<7>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) { + if (updated) { + mock_image_ctx.op_work_queue->queue(ctx, ret_val); + } + return updated; + }))); + } + } + + void expect_get_parent_overlap(MockTestImageCtx &mock_image_ctx, + uint64_t overlap) { + EXPECT_CALL(mock_image_ctx, get_parent_overlap(CEPH_NOSNAP, _)) + .WillOnce(WithArg<1>(Invoke([overlap](uint64_t *o) { + *o = overlap; + return 0; + }))); + } + + void expect_object_may_exist(MockTestImageCtx &mock_image_ctx, + uint64_t object_no, bool exists) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, object_may_exist(object_no)) + .WillOnce(Return(exists)); + } + } + + void expect_get_object_name(MockTestImageCtx &mock_image_ctx, + uint64_t object_no, const std::string& oid) { + EXPECT_CALL(mock_image_ctx, get_object_name(object_no)) + .WillOnce(Return(oid)); + } + + void expect_aio_remove(MockTestImageCtx &mock_image_ctx, + const std::string& oid, int ret_val) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), remove(oid, _)) + .WillOnce(Return(ret_val)); + } + + void expect_object_discard(MockImageCtx &mock_image_ctx, + io::MockObjectDispatch& mock_io_object_dispatch, + uint64_t offset, uint64_t length, + bool update_object_map, int r) { + EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, send(_)) + .WillOnce(Invoke([&mock_image_ctx, offset, length, update_object_map, r] + (io::ObjectDispatchSpec* spec) { + auto discard = boost::apply_visitor(io::DiscardVisitor(), spec->request); + ASSERT_TRUE(discard != nullptr); + ASSERT_EQ(offset, discard->object_off); + ASSERT_EQ(length, discard->object_len); + int flags = 0; + if (!update_object_map) { + flags = io::OBJECT_DISCARD_FLAG_DISABLE_OBJECT_MAP_UPDATE; + } + ASSERT_EQ(flags, discard->discard_flags); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + mock_image_ctx.op_work_queue->queue(&spec->dispatcher_ctx, r); + })); + } +}; + +TEST_F(TestMockOperationTrimRequest, SuccessRemove) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_PENDING, OBJECT_EXISTS, + true, 0); + + // copy-up + expect_get_parent_overlap(mock_image_ctx, 0); + + // remove + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_aio_remove(mock_image_ctx, "object0", 0); + + // post + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_NONEXISTENT, + OBJECT_PENDING, true, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, m_image_size, 0, progress_ctx); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, SuccessCopyUp) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING) + ASSERT_EQ(0, create_snapshot("snap1")); + + int order = 22; + uint64_t features; + ASSERT_TRUE(::get_features(&features)); + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 2, OBJECT_PENDING, OBJECT_EXISTS, + true, 0); + + // copy-up + io::MockObjectDispatch mock_io_object_dispatch; + expect_get_parent_overlap(mock_image_ctx, ictx->get_object_size()); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 0, + ictx->get_object_size(), false, 0); + + // remove + expect_object_may_exist(mock_image_ctx, 1, true); + expect_get_object_name(mock_image_ctx, 1, "object1"); + expect_aio_remove(mock_image_ctx, "object1", 0); + + // post + expect_object_map_update(mock_image_ctx, 0, 2, OBJECT_NONEXISTENT, + OBJECT_PENDING, true, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, 2 * ictx->get_object_size(), 0, progress_ctx); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, SuccessBoundary) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // boundary + io::MockObjectDispatch mock_io_object_dispatch; + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 1, + ictx->get_object_size() - 1, true, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, ictx->get_object_size(), 1, progress_ctx); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, SuccessNoOp) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); +} + +TEST_F(TestMockOperationTrimRequest, RemoveError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_PENDING, OBJECT_EXISTS, + false, 0); + + // copy-up + expect_get_parent_overlap(mock_image_ctx, 0); + + // remove + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_aio_remove(mock_image_ctx, "object0", -EPERM); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, m_image_size, 0, progress_ctx); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EPERM, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, CopyUpError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING) + ASSERT_EQ(0, create_snapshot("snap1")); + + int order = 22; + uint64_t features; + ASSERT_TRUE(::get_features(&features)); + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 2, OBJECT_PENDING, OBJECT_EXISTS, + false, 0); + + // copy-up + io::MockObjectDispatch mock_io_object_dispatch; + expect_get_parent_overlap(mock_image_ctx, ictx->get_object_size()); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 0, + ictx->get_object_size(), false, -EINVAL); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, 2 * ictx->get_object_size(), 0, progress_ctx); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, BoundaryError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // boundary + io::MockObjectDispatch mock_io_object_dispatch; + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 1, + ictx->get_object_size() - 1, true, -EINVAL); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, ictx->get_object_size(), 1, progress_ctx); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/rbdrw.py b/src/test/librbd/rbdrw.py new file mode 100644 index 00000000..8dbbda24 --- /dev/null +++ b/src/test/librbd/rbdrw.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +""" +Loop writing/reading the first 4k of image argv[1] in pool rbd, +after acquiring exclusive lock named argv[2]. When an exception +happens, split off the last number in the exception 'args' string +and use it as the process exit code, if it's convertible to a number. + +Designed to run against a blacklist operation and verify the +ESHUTDOWN expected from the image operation. + +Note: this cannot be run with writeback caching on, currently, as +writeback errors cause reads be marked dirty rather than error, and +even if they were marked as errored, ObjectCacher would retry them +rather than note them as errored. +""" + +import rados, rbd, sys + +with rados.Rados(conffile='') as r: + with r.open_ioctx('rbd') as ioctx: + try: + with rbd.Image(ioctx, sys.argv[1]) as image: + image.lock_exclusive(sys.argv[2]) + while True: + image.write(b'A' * 4096, 0) + r = image.read(0, 4096) + except rbd.ConnectionShutdown: + # it so happens that the errno here is 108, but + # anything recognizable would do + exit(108) diff --git a/src/test/librbd/test_BlockGuard.cc b/src/test/librbd/test_BlockGuard.cc new file mode 100644 index 00000000..e41e5882 --- /dev/null +++ b/src/test/librbd/test_BlockGuard.cc @@ -0,0 +1,98 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "librbd/BlockGuard.h" + +namespace librbd { + +class TestIOBlockGuard : public TestFixture { +public: + static uint32_t s_index; + + struct Operation { + uint32_t index; + Operation() : index(++s_index) { + } + Operation(Operation &&rhs) : index(rhs.index) { + } + Operation(const Operation &) = delete; + + Operation& operator=(Operation &&rhs) { + index = rhs.index; + return *this; + } + + bool operator==(const Operation &rhs) const { + return index == rhs.index; + } + }; + + typedef std::list<Operation> Operations; + + typedef BlockGuard<Operation> OpBlockGuard; + + void SetUp() override { + TestFixture::SetUp(); + m_cct = reinterpret_cast<CephContext*>(m_ioctx.cct()); + } + + CephContext *m_cct; +}; + +TEST_F(TestIOBlockGuard, NonDetainedOps) { + OpBlockGuard op_block_guard(m_cct); + + Operation op1; + BlockGuardCell *cell1; + ASSERT_EQ(0, op_block_guard.detain({1, 3}, &op1, &cell1)); + + Operation op2; + BlockGuardCell *cell2; + ASSERT_EQ(0, op_block_guard.detain({0, 1}, &op2, &cell2)); + + Operation op3; + BlockGuardCell *cell3; + ASSERT_EQ(0, op_block_guard.detain({3, 6}, &op3, &cell3)); + + Operations released_ops; + op_block_guard.release(cell1, &released_ops); + ASSERT_TRUE(released_ops.empty()); + + op_block_guard.release(cell2, &released_ops); + ASSERT_TRUE(released_ops.empty()); + + op_block_guard.release(cell3, &released_ops); + ASSERT_TRUE(released_ops.empty()); +} + +TEST_F(TestIOBlockGuard, DetainedOps) { + OpBlockGuard op_block_guard(m_cct); + + Operation op1; + BlockGuardCell *cell1; + ASSERT_EQ(0, op_block_guard.detain({1, 3}, &op1, &cell1)); + + Operation op2; + BlockGuardCell *cell2; + ASSERT_EQ(1, op_block_guard.detain({2, 6}, &op2, &cell2)); + ASSERT_EQ(nullptr, cell2); + + Operation op3; + BlockGuardCell *cell3; + ASSERT_EQ(2, op_block_guard.detain({0, 2}, &op3, &cell3)); + ASSERT_EQ(nullptr, cell3); + + Operations expected_ops; + expected_ops.push_back(std::move(op2)); + expected_ops.push_back(std::move(op3)); + Operations released_ops; + op_block_guard.release(cell1, &released_ops); + ASSERT_EQ(expected_ops, released_ops); +} + +uint32_t TestIOBlockGuard::s_index = 0; + +} // namespace librbd + diff --git a/src/test/librbd/test_DeepCopy.cc b/src/test/librbd/test_DeepCopy.cc new file mode 100644 index 00000000..aa00bdf1 --- /dev/null +++ b/src/test/librbd/test_DeepCopy.cc @@ -0,0 +1,757 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "librbd/Operations.h" +#include "librbd/api/Image.h" +#include "librbd/internal.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/io/ReadResult.h" + +void register_test_deep_copy() { +} + +struct TestDeepCopy : public TestFixture { + void SetUp() override { + TestFixture::SetUp(); + + std::string image_name = get_temp_image_name(); + int order = 22; + uint64_t size = (1 << order) * 20; + uint64_t features = 0; + bool old_format = !get_features(&features); + EXPECT_EQ(0, create_image_full_pp(m_rbd, m_ioctx, image_name, size, + features, old_format, &order)); + ASSERT_EQ(0, open_image(image_name, &m_src_ictx)); + + if (old_format) { + // The destination should always be in the new format. + uint64_t format = 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_FORMAT, format)); + } + } + + void TearDown() override { + if (m_src_ictx != nullptr) { + deep_copy(); + if (m_dst_ictx != nullptr) { + compare(); + close_image(m_dst_ictx); + } + close_image(m_src_ictx); + } + + TestFixture::TearDown(); + } + + void deep_copy() { + std::string dst_name = get_temp_image_name(); + librbd::NoOpProgressContext no_op; + EXPECT_EQ(0, m_src_ictx->io_work_queue->flush()); + EXPECT_EQ(0, librbd::api::Image<>::deep_copy(m_src_ictx, m_src_ictx->md_ctx, + dst_name.c_str(), m_opts, + no_op)); + EXPECT_EQ(0, open_image(dst_name, &m_dst_ictx)); + } + + void compare() { + vector<librbd::snap_info_t> src_snaps, dst_snaps; + + EXPECT_EQ(m_src_ictx->size, m_dst_ictx->size); + EXPECT_EQ(0, librbd::snap_list(m_src_ictx, src_snaps)); + EXPECT_EQ(0, librbd::snap_list(m_dst_ictx, dst_snaps)); + EXPECT_EQ(src_snaps.size(), dst_snaps.size()); + for (size_t i = 0; i <= src_snaps.size(); i++) { + const char *src_snap_name = nullptr; + const char *dst_snap_name = nullptr; + if (i < src_snaps.size()) { + EXPECT_EQ(src_snaps[i].name, dst_snaps[i].name); + src_snap_name = src_snaps[i].name.c_str(); + dst_snap_name = dst_snaps[i].name.c_str(); + } + EXPECT_EQ(0, librbd::api::Image<>::snap_set( + m_src_ictx, cls::rbd::UserSnapshotNamespace(), + src_snap_name)); + EXPECT_EQ(0, librbd::api::Image<>::snap_set( + m_dst_ictx, cls::rbd::UserSnapshotNamespace(), + dst_snap_name)); + uint64_t src_size, dst_size; + { + RWLock::RLocker src_locker(m_src_ictx->snap_lock); + RWLock::RLocker dst_locker(m_dst_ictx->snap_lock); + src_size = m_src_ictx->get_image_size(m_src_ictx->snap_id); + dst_size = m_dst_ictx->get_image_size(m_dst_ictx->snap_id); + } + EXPECT_EQ(src_size, dst_size); + + if (m_dst_ictx->test_features(RBD_FEATURE_LAYERING)) { + bool flags_set; + RWLock::RLocker dst_locker(m_dst_ictx->snap_lock); + EXPECT_EQ(0, m_dst_ictx->test_flags(m_dst_ictx->snap_id, + RBD_FLAG_OBJECT_MAP_INVALID, + m_dst_ictx->snap_lock, &flags_set)); + EXPECT_FALSE(flags_set); + } + + ssize_t read_size = 1 << m_src_ictx->order; + uint64_t offset = 0; + while (offset < src_size) { + read_size = std::min(read_size, static_cast<ssize_t>(src_size - offset)); + + bufferptr src_ptr(read_size); + bufferlist src_bl; + src_bl.push_back(src_ptr); + librbd::io::ReadResult src_result{&src_bl}; + EXPECT_EQ(read_size, m_src_ictx->io_work_queue->read( + offset, read_size, librbd::io::ReadResult{src_result}, 0)); + + bufferptr dst_ptr(read_size); + bufferlist dst_bl; + dst_bl.push_back(dst_ptr); + librbd::io::ReadResult dst_result{&dst_bl}; + EXPECT_EQ(read_size, m_dst_ictx->io_work_queue->read( + offset, read_size, librbd::io::ReadResult{dst_result}, 0)); + + if (!src_bl.contents_equal(dst_bl)) { + std::cout << "snap: " << (src_snap_name ? src_snap_name : "null") + << ", block " << offset << "~" << read_size << " differs" + << std::endl; + // std::cout << "src block: " << std::endl; src_bl.hexdump(std::cout); + // std::cout << "dst block: " << std::endl; dst_bl.hexdump(std::cout); + } + EXPECT_TRUE(src_bl.contents_equal(dst_bl)); + offset += read_size; + } + } + } + + void test_no_snaps() { + bufferlist bl; + bl.append(std::string(((1 << m_src_ictx->order) * 2) + 1, '1')); + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(0 * bl.length(), bl.length(), + bufferlist{bl}, 0)); + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(2 * bl.length(), bl.length(), + bufferlist{bl}, 0)); + } + + void test_snaps() { + bufferlist bl; + bl.append(std::string(((1 << m_src_ictx->order) * 2) + 1, '1')); + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(0 * bl.length(), bl.length(), + bufferlist{bl}, 0)); + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap1")); + + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(1 * bl.length(), bl.length(), + bufferlist{bl}, 0)); + bufferlist bl1; + bl1.append(std::string(1000, 'X')); + ASSERT_EQ(static_cast<ssize_t>(bl1.length()), + m_src_ictx->io_work_queue->write(0 * bl.length(), bl1.length(), + bufferlist{bl1}, 0)); + ASSERT_EQ(static_cast<ssize_t>(bl1.length()), + m_src_ictx->io_work_queue->discard(bl1.length() + 10, + bl1.length(), false)); + + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap2")); + ASSERT_EQ(static_cast<ssize_t>(bl1.length()), + m_src_ictx->io_work_queue->write(1 * bl.length(), bl1.length(), + bufferlist{bl1}, 0)); + ASSERT_EQ(static_cast<ssize_t>(bl1.length()), + m_src_ictx->io_work_queue->discard(2 * bl1.length() + 10, + bl1.length(), false)); + } + + void test_snap_discard() { + bufferlist bl; + bl.append(std::string(100, '1')); + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(0, bl.length(), bufferlist{bl}, + 0)); + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap")); + + size_t len = (1 << m_src_ictx->order) * 2; + ASSERT_EQ(static_cast<ssize_t>(len), + m_src_ictx->io_work_queue->discard(0, len, false)); + } + + void test_clone_discard() { + bufferlist bl; + bl.append(std::string(100, '1')); + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(0, bl.length(), bufferlist{bl}, + 0)); + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap")); + ASSERT_EQ(0, snap_protect(*m_src_ictx, "snap")); + + std::string clone_name = get_temp_image_name(); + int order = m_src_ictx->order; + uint64_t features; + ASSERT_EQ(0, librbd::get_features(m_src_ictx, &features)); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_src_ictx->name.c_str(), "snap", + m_ioctx, clone_name.c_str(), features, &order, 0, + 0)); + close_image(m_src_ictx); + ASSERT_EQ(0, open_image(clone_name, &m_src_ictx)); + + size_t len = (1 << m_src_ictx->order) * 2; + ASSERT_EQ(static_cast<ssize_t>(len), + m_src_ictx->io_work_queue->discard(0, len, false)); + } + + void test_clone_shrink() { + bufferlist bl; + bl.append(std::string(100, '1')); + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(0, bl.length(), bufferlist{bl}, + 0)); + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap")); + ASSERT_EQ(0, snap_protect(*m_src_ictx, "snap")); + + std::string clone_name = get_temp_image_name(); + int order = m_src_ictx->order; + uint64_t features; + ASSERT_EQ(0, librbd::get_features(m_src_ictx, &features)); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_src_ictx->name.c_str(), "snap", + m_ioctx, clone_name.c_str(), features, &order, 0, + 0)); + close_image(m_src_ictx); + ASSERT_EQ(0, open_image(clone_name, &m_src_ictx)); + + librbd::NoOpProgressContext no_op; + auto new_size = m_src_ictx->size >> 1; + ASSERT_EQ(0, m_src_ictx->operations->resize(new_size, true, no_op)); + } + + void test_clone_expand() { + bufferlist bl; + bl.append(std::string(100, '1')); + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(0, bl.length(), bufferlist{bl}, + 0)); + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap")); + ASSERT_EQ(0, snap_protect(*m_src_ictx, "snap")); + + std::string clone_name = get_temp_image_name(); + int order = m_src_ictx->order; + uint64_t features; + ASSERT_EQ(0, librbd::get_features(m_src_ictx, &features)); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_src_ictx->name.c_str(), "snap", + m_ioctx, clone_name.c_str(), features, &order, 0, + 0)); + close_image(m_src_ictx); + ASSERT_EQ(0, open_image(clone_name, &m_src_ictx)); + + librbd::NoOpProgressContext no_op; + auto new_size = m_src_ictx->size << 1; + ASSERT_EQ(0, m_src_ictx->operations->resize(new_size, true, no_op)); + } + + void test_clone_hide_parent() { + uint64_t object_size = 1 << m_src_ictx->order; + bufferlist bl; + bl.append(std::string(100, '1')); + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(object_size, bl.length(), + bufferlist{bl}, 0)); + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap")); + ASSERT_EQ(0, snap_protect(*m_src_ictx, "snap")); + + std::string clone_name = get_temp_image_name(); + int order = m_src_ictx->order; + uint64_t features; + ASSERT_EQ(0, librbd::get_features(m_src_ictx, &features)); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_src_ictx->name.c_str(), "snap", + m_ioctx, clone_name.c_str(), features, &order, 0, + 0)); + close_image(m_src_ictx); + ASSERT_EQ(0, open_image(clone_name, &m_src_ictx)); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap1")); + + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->discard(object_size, bl.length(), + false)); + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap2")); + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, m_src_ictx->operations->resize(object_size, true, no_op)); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap3")); + + ASSERT_EQ(0, m_src_ictx->operations->resize(2 * object_size, true, no_op)); + } + + void test_clone() { + bufferlist bl; + bl.append(std::string(((1 << m_src_ictx->order) * 2) + 1, '1')); + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(0 * bl.length(), bl.length(), + bufferlist{bl}, 0)); + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(2 * bl.length(), bl.length(), + bufferlist{bl}, 0)); + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap")); + ASSERT_EQ(0, snap_protect(*m_src_ictx, "snap")); + + std::string clone_name = get_temp_image_name(); + int order = m_src_ictx->order; + uint64_t features; + ASSERT_EQ(0, librbd::get_features(m_src_ictx, &features)); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_src_ictx->name.c_str(), "snap", + m_ioctx, clone_name.c_str(), features, &order, 0, + 0)); + close_image(m_src_ictx); + ASSERT_EQ(0, open_image(clone_name, &m_src_ictx)); + + bufferlist bl1; + bl1.append(std::string(1000, 'X')); + ASSERT_EQ(static_cast<ssize_t>(bl1.length()), + m_src_ictx->io_work_queue->write(0 * bl.length(), bl1.length(), + bufferlist{bl1}, 0)); + ASSERT_EQ(static_cast<ssize_t>(bl1.length()), + m_src_ictx->io_work_queue->discard(bl1.length() + 10, + bl1.length(), false)); + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap")); + ASSERT_EQ(0, snap_protect(*m_src_ictx, "snap")); + + clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_src_ictx->name.c_str(), "snap", + m_ioctx, clone_name.c_str(), features, &order, 0, + 0)); + close_image(m_src_ictx); + ASSERT_EQ(0, open_image(clone_name, &m_src_ictx)); + + ASSERT_EQ(static_cast<ssize_t>(bl1.length()), + m_src_ictx->io_work_queue->write(1 * bl.length(), bl1.length(), + bufferlist{bl1}, 0)); + ASSERT_EQ(static_cast<ssize_t>(bl1.length()), + m_src_ictx->io_work_queue->discard(2 * bl1.length() + 10, + bl1.length(), false)); + } + + void test_stress() { + uint64_t initial_size, size; + { + RWLock::RLocker src_locker(m_src_ictx->snap_lock); + size = initial_size = m_src_ictx->get_image_size(CEPH_NOSNAP); + } + + int nsnaps = 4; + const char *c = getenv("TEST_RBD_DEEPCOPY_STRESS_NSNAPS"); + if (c != NULL) { + std::stringstream ss(c); + ASSERT_TRUE(ss >> nsnaps); + } + + int nwrites = 4; + c = getenv("TEST_RBD_DEEPCOPY_STRESS_NWRITES"); + if (c != NULL) { + std::stringstream ss(c); + ASSERT_TRUE(ss >> nwrites); + } + + for (int i = 0; i < nsnaps; i++) { + for (int j = 0; j < nwrites; j++) { + size_t len = rand() % ((1 << m_src_ictx->order) * 2); + ASSERT_GT(size, len); + bufferlist bl; + bl.append(std::string(len, static_cast<char>('A' + i))); + uint64_t off = std::min(static_cast<uint64_t>(rand() % size), + static_cast<uint64_t>(size - len)); + std::cout << "write: " << static_cast<char>('A' + i) << " " << off + << "~" << len << std::endl; + ASSERT_EQ(static_cast<ssize_t>(bl.length()), + m_src_ictx->io_work_queue->write(off, bl.length(), + bufferlist{bl}, 0)); + len = rand() % ((1 << m_src_ictx->order) * 2); + ASSERT_GT(size, len); + off = std::min(static_cast<uint64_t>(rand() % size), + static_cast<uint64_t>(size - len)); + std::cout << "discard: " << off << "~" << len << std::endl; + ASSERT_EQ(static_cast<ssize_t>(len), + m_src_ictx->io_work_queue->discard(off, len, false)); + } + + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + std::string snap_name = "snap" + stringify(i); + std::cout << "snap: " << snap_name << std::endl; + ASSERT_EQ(0, snap_create(*m_src_ictx, snap_name.c_str())); + + if (m_src_ictx->test_features(RBD_FEATURE_LAYERING) && rand() % 4) { + ASSERT_EQ(0, snap_protect(*m_src_ictx, snap_name.c_str())); + + std::string clone_name = get_temp_image_name(); + int order = m_src_ictx->order; + uint64_t features; + ASSERT_EQ(0, librbd::get_features(m_src_ictx, &features)); + + std::cout << "clone " << m_src_ictx->name << " -> " << clone_name + << std::endl; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_src_ictx->name.c_str(), + snap_name.c_str(), m_ioctx, + clone_name.c_str(), features, &order, + m_src_ictx->stripe_unit, + m_src_ictx->stripe_count)); + close_image(m_src_ictx); + ASSERT_EQ(0, open_image(clone_name, &m_src_ictx)); + } + + if (rand() % 2) { + librbd::NoOpProgressContext no_op; + uint64_t new_size = initial_size + rand() % size; + std::cout << "resize: " << new_size << std::endl; + ASSERT_EQ(0, m_src_ictx->operations->resize(new_size, true, no_op)); + { + RWLock::RLocker src_locker(m_src_ictx->snap_lock); + size = m_src_ictx->get_image_size(CEPH_NOSNAP); + } + ASSERT_EQ(new_size, size); + } + } + } + + librbd::ImageCtx *m_src_ictx = nullptr; + librbd::ImageCtx *m_dst_ictx = nullptr; + librbd::ImageOptions m_opts; +}; + +TEST_F(TestDeepCopy, Empty) +{ +} + +TEST_F(TestDeepCopy, NoSnaps) +{ + test_no_snaps(); +} + +TEST_F(TestDeepCopy, Snaps) +{ + test_snaps(); +} + +TEST_F(TestDeepCopy, SnapDiscard) +{ + test_snap_discard(); +} + +TEST_F(TestDeepCopy, CloneDiscard) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + test_clone_discard(); +} + +TEST_F(TestDeepCopy, CloneShrink) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + test_clone_shrink(); +} + +TEST_F(TestDeepCopy, CloneExpand) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + test_clone_expand(); +} + +TEST_F(TestDeepCopy, CloneHideParent) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + test_clone_hide_parent(); +} + +TEST_F(TestDeepCopy, Clone) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + test_clone(); +} + +TEST_F(TestDeepCopy, CloneFlatten) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + uint64_t flatten = 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_FLATTEN, flatten)); + + test_clone(); +} + +TEST_F(TestDeepCopy, Stress) +{ + test_stress(); +} + +TEST_F(TestDeepCopy, NoSnaps_LargerDstObjSize) +{ + uint64_t order = m_src_ictx->order + 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + + test_no_snaps(); +} + +TEST_F(TestDeepCopy, Snaps_LargerDstObjSize) +{ + uint64_t order = m_src_ictx->order + 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + + test_snaps(); +} + +TEST_F(TestDeepCopy, Clone_LargerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + uint64_t order = m_src_ictx->order + 1 + rand() % 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + + test_clone(); +} + +TEST_F(TestDeepCopy, CloneFlatten_LargerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + uint64_t order = m_src_ictx->order + 1 + rand() % 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + uint64_t flatten = 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_FLATTEN, flatten)); + + test_clone(); +} + +TEST_F(TestDeepCopy, Stress_LargerDstObjSize) +{ + uint64_t order = m_src_ictx->order + 1 + rand() % 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + + test_stress(); +} + +TEST_F(TestDeepCopy, NoSnaps_SmallerDstObjSize) +{ + uint64_t order = m_src_ictx->order - 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + uint64_t stripe_unit = m_src_ictx->stripe_unit >> 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + + test_no_snaps(); +} + +TEST_F(TestDeepCopy, Snaps_SmallerDstObjSize) +{ + uint64_t order = m_src_ictx->order - 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + uint64_t stripe_unit = m_src_ictx->stripe_unit >> 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + + test_snaps(); +} + +TEST_F(TestDeepCopy, Clone_SmallerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + uint64_t order = m_src_ictx->order - 1 - rand() % 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + uint64_t stripe_unit = m_src_ictx->stripe_unit >> 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + + test_clone(); +} + +TEST_F(TestDeepCopy, CloneFlatten_SmallerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + uint64_t order = m_src_ictx->order - 1 - rand() % 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + uint64_t stripe_unit = m_src_ictx->stripe_unit >> 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + uint64_t flatten = 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_FLATTEN, flatten)); + + test_clone(); +} + +TEST_F(TestDeepCopy, Stress_SmallerDstObjSize) +{ + uint64_t order = m_src_ictx->order - 1 - rand() % 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + uint64_t stripe_unit = m_src_ictx->stripe_unit >> 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + + test_stress(); +} + +TEST_F(TestDeepCopy, NoSnaps_StrippingLargerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order + 1; + uint64_t stripe_unit = 1 << (order - 2); + uint64_t stripe_count = 4; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_no_snaps(); +} + +TEST_F(TestDeepCopy, Snaps_StrippingLargerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order + 1; + uint64_t stripe_unit = 1 << (order - 2); + uint64_t stripe_count = 4; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_snaps(); +} + +TEST_F(TestDeepCopy, Clone_StrippingLargerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order + 1; + uint64_t stripe_unit = 1 << (order - 2); + uint64_t stripe_count = 4; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_clone(); +} + +TEST_F(TestDeepCopy, CloneFlatten_StrippingLargerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order + 1; + uint64_t stripe_unit = 1 << (order - 2); + uint64_t stripe_count = 4; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + uint64_t flatten = 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_FLATTEN, flatten)); + + test_clone(); +} + +TEST_F(TestDeepCopy, Stress_StrippingLargerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order + 1 + rand() % 2; + uint64_t stripe_unit = 1 << (order - rand() % 4); + uint64_t stripe_count = 2 + rand() % 14; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_stress(); +} + +TEST_F(TestDeepCopy, NoSnaps_StrippingSmallerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order - 1; + uint64_t stripe_unit = 1 << (order - 2); + uint64_t stripe_count = 4; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_no_snaps(); +} + +TEST_F(TestDeepCopy, Snaps_StrippingSmallerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order - 1; + uint64_t stripe_unit = 1 << (order - 2); + uint64_t stripe_count = 4; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_snaps(); +} + +TEST_F(TestDeepCopy, Clone_StrippingSmallerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order - 1 - rand() % 2; + uint64_t stripe_unit = 1 << (order - rand() % 4); + uint64_t stripe_count = 2 + rand() % 14; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_clone(); +} + +TEST_F(TestDeepCopy, CloneFlatten_StrippingSmallerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order - 1 - rand() % 2; + uint64_t stripe_unit = 1 << (order - rand() % 4); + uint64_t stripe_count = 2 + rand() % 14; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + uint64_t flatten = 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_FLATTEN, flatten)); + + test_clone(); +} + +TEST_F(TestDeepCopy, Stress_StrippingSmallerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order - 1 - rand() % 2; + uint64_t stripe_unit = 1 << (order - rand() % 4); + uint64_t stripe_count = 2 + rand() % 14; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_stress(); +} diff --git a/src/test/librbd/test_Groups.cc b/src/test/librbd/test_Groups.cc new file mode 100644 index 00000000..ce3cf2a5 --- /dev/null +++ b/src/test/librbd/test_Groups.cc @@ -0,0 +1,321 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "include/rbd/librbd.h" +#include "include/rbd/librbd.hpp" +#include "test/librados/test.h" +#include "gtest/gtest.h" + +#include <boost/scope_exit.hpp> +#include <vector> + +void register_test_groups() { +} + +class TestGroup : public TestFixture { + +}; + +TEST_F(TestGroup, group_create) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, _pool_name.c_str(), &ioctx); + BOOST_SCOPE_EXIT(ioctx) { + rados_ioctx_destroy(ioctx); + } BOOST_SCOPE_EXIT_END; + + librbd::RBD rbd; + ASSERT_EQ(0, rbd_group_create(ioctx, "mygroup")); + + size_t size = 0; + ASSERT_EQ(-ERANGE, rbd_group_list(ioctx, NULL, &size)); + ASSERT_EQ(strlen("mygroup") + 1, size); + + char groups[80]; + ASSERT_EQ(static_cast<int>(strlen("mygroup") + 1), + rbd_group_list(ioctx, groups, &size)); + ASSERT_STREQ("mygroup", groups); + + ASSERT_EQ(0, rbd_group_remove(ioctx, "mygroup")); + + ASSERT_EQ(0, rbd_group_list(ioctx, groups, &size)); +} + +TEST_F(TestGroup, group_createPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + ASSERT_EQ(0, rbd.group_create(ioctx, "mygroup")); + + vector<string> groups; + ASSERT_EQ(0, rbd.group_list(ioctx, &groups)); + ASSERT_EQ(1U, groups.size()); + ASSERT_EQ("mygroup", groups[0]); + + groups.clear(); + ASSERT_EQ(0, rbd.group_rename(ioctx, "mygroup", "newgroup")); + ASSERT_EQ(0, rbd.group_list(ioctx, &groups)); + ASSERT_EQ(1U, groups.size()); + ASSERT_EQ("newgroup", groups[0]); + + ASSERT_EQ(0, rbd.group_remove(ioctx, "newgroup")); + + groups.clear(); + ASSERT_EQ(0, rbd.group_list(ioctx, &groups)); + ASSERT_EQ(0U, groups.size()); +} + +TEST_F(TestGroup, add_image) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, _pool_name.c_str(), &ioctx); + BOOST_SCOPE_EXIT(ioctx) { + rados_ioctx_destroy(ioctx); + } BOOST_SCOPE_EXIT_END; + + const char *group_name = "mycg"; + ASSERT_EQ(0, rbd_group_create(ioctx, group_name)); + + rbd_image_t image; + ASSERT_EQ(0, rbd_open(ioctx, m_image_name.c_str(), &image, NULL)); + BOOST_SCOPE_EXIT(image) { + EXPECT_EQ(0, rbd_close(image)); + } BOOST_SCOPE_EXIT_END; + + uint64_t features; + ASSERT_EQ(0, rbd_get_features(image, &features)); + ASSERT_TRUE((features & RBD_FEATURE_OPERATIONS) == 0ULL); + + uint64_t op_features; + ASSERT_EQ(0, rbd_get_op_features(image, &op_features)); + ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_GROUP) == 0ULL); + + ASSERT_EQ(0, rbd_group_image_add(ioctx, group_name, ioctx, + m_image_name.c_str())); + + ASSERT_EQ(0, rbd_get_features(image, &features)); + ASSERT_TRUE((features & RBD_FEATURE_OPERATIONS) == + RBD_FEATURE_OPERATIONS); + ASSERT_EQ(0, rbd_get_op_features(image, &op_features)); + ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_GROUP) == + RBD_OPERATION_FEATURE_GROUP); + + size_t num_images = 0; + ASSERT_EQ(-ERANGE, rbd_group_image_list(ioctx, group_name, NULL, + sizeof(rbd_group_image_info_t), + &num_images)); + ASSERT_EQ(1U, num_images); + + rbd_group_image_info_t images[1]; + ASSERT_EQ(1, rbd_group_image_list(ioctx, group_name, images, + sizeof(rbd_group_image_info_t), + &num_images)); + + ASSERT_EQ(m_image_name, images[0].name); + ASSERT_EQ(rados_ioctx_get_id(ioctx), images[0].pool); + + ASSERT_EQ(0, rbd_group_image_list_cleanup(images, + sizeof(rbd_group_image_info_t), + num_images)); + ASSERT_EQ(0, rbd_group_image_remove(ioctx, group_name, ioctx, + m_image_name.c_str())); + + ASSERT_EQ(0, rbd_get_features(image, &features)); + ASSERT_TRUE((features & RBD_FEATURE_OPERATIONS) == 0ULL); + ASSERT_EQ(0, rbd_get_op_features(image, &op_features)); + ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_GROUP) == 0ULL); + + ASSERT_EQ(0, rbd_group_image_list(ioctx, group_name, images, + sizeof(rbd_group_image_info_t), + &num_images)); + ASSERT_EQ(0U, num_images); + + ASSERT_EQ(0, rbd_group_remove(ioctx, group_name)); +} + +TEST_F(TestGroup, add_imagePP) +{ + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx)); + + const char *group_name = "mycg"; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.group_create(ioctx, group_name)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, m_image_name.c_str(), NULL)); + + uint64_t features; + ASSERT_EQ(0, image.features(&features)); + ASSERT_TRUE((features & RBD_FEATURE_OPERATIONS) == 0ULL); + + uint64_t op_features; + ASSERT_EQ(0, image.get_op_features(&op_features)); + ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_GROUP) == 0ULL); + + ASSERT_EQ(0, rbd.group_image_add(ioctx, group_name, ioctx, + m_image_name.c_str())); + + ASSERT_EQ(0, image.features(&features)); + ASSERT_TRUE((features & RBD_FEATURE_OPERATIONS) == + RBD_FEATURE_OPERATIONS); + ASSERT_EQ(0, image.get_op_features(&op_features)); + ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_GROUP) == + RBD_OPERATION_FEATURE_GROUP); + + vector<librbd::group_image_info_t> images; + ASSERT_EQ(0, rbd.group_image_list(ioctx, group_name, &images, + sizeof(librbd::group_image_info_t))); + ASSERT_EQ(1U, images.size()); + ASSERT_EQ(m_image_name, images[0].name); + ASSERT_EQ(ioctx.get_id(), images[0].pool); + + ASSERT_EQ(0, rbd.group_image_remove(ioctx, group_name, ioctx, + m_image_name.c_str())); + + ASSERT_EQ(0, image.features(&features)); + ASSERT_TRUE((features & RBD_FEATURE_OPERATIONS) == 0ULL); + ASSERT_EQ(0, image.get_op_features(&op_features)); + ASSERT_TRUE((op_features & RBD_OPERATION_FEATURE_GROUP) == 0ULL); + + images.clear(); + ASSERT_EQ(0, rbd.group_image_list(ioctx, group_name, &images, + sizeof(librbd::group_image_info_t))); + ASSERT_EQ(0U, images.size()); + + ASSERT_EQ(0, rbd.group_remove(ioctx, group_name)); +} + +TEST_F(TestGroup, add_snapshot) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, _pool_name.c_str(), &ioctx); + BOOST_SCOPE_EXIT(ioctx) { + rados_ioctx_destroy(ioctx); + } BOOST_SCOPE_EXIT_END; + + const char *group_name = "snap_group"; + const char *snap_name = "snap_snapshot"; + + const char orig_data[] = "orig data"; + const char test_data[] = "test data"; + char read_data[10]; + + rbd_image_t image; + ASSERT_EQ(0, rbd_open(ioctx, m_image_name.c_str(), &image, NULL)); + BOOST_SCOPE_EXIT(image) { + EXPECT_EQ(0, rbd_close(image)); + } BOOST_SCOPE_EXIT_END; + + ASSERT_EQ(10, rbd_write(image, 0, 10, orig_data)); + ASSERT_EQ(10, rbd_read(image, 0, 10, read_data)); + ASSERT_EQ(0, memcmp(orig_data, read_data, 10)); + + ASSERT_EQ(0, rbd_group_create(ioctx, group_name)); + + ASSERT_EQ(0, rbd_group_image_add(ioctx, group_name, ioctx, + m_image_name.c_str())); + + ASSERT_EQ(0, rbd_group_snap_create(ioctx, group_name, snap_name)); + + size_t num_snaps = 0; + ASSERT_EQ(-ERANGE, rbd_group_snap_list(ioctx, group_name, NULL, + sizeof(rbd_group_snap_info_t), + &num_snaps)); + ASSERT_EQ(1U, num_snaps); + + rbd_group_snap_info_t snaps[1]; + ASSERT_EQ(1, rbd_group_snap_list(ioctx, group_name, snaps, + sizeof(rbd_group_snap_info_t), + &num_snaps)); + + ASSERT_STREQ(snap_name, snaps[0].name); + + ASSERT_EQ(10, rbd_write(image, 11, 10, test_data)); + ASSERT_EQ(10, rbd_read(image, 11, 10, read_data)); + ASSERT_EQ(0, memcmp(test_data, read_data, 10)); + + ASSERT_EQ(0, rbd_group_snap_rollback(ioctx, group_name, snap_name)); + ASSERT_EQ(10, rbd_read(image, 0, 10, read_data)); + ASSERT_EQ(0, memcmp(orig_data, read_data, 10)); + + ASSERT_EQ(0, rbd_group_snap_list_cleanup(snaps, sizeof(rbd_group_snap_info_t), + num_snaps)); + ASSERT_EQ(0, rbd_group_snap_remove(ioctx, group_name, snap_name)); + + ASSERT_EQ(0, rbd_group_snap_list(ioctx, group_name, snaps, + sizeof(rbd_group_snap_info_t), + &num_snaps)); + ASSERT_EQ(0U, num_snaps); + + ASSERT_EQ(0, rbd_group_remove(ioctx, group_name)); +} + +TEST_F(TestGroup, add_snapshotPP) +{ + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx)); + + const char *group_name = "snap_group"; + const char *snap_name = "snap_snapshot"; + + librbd::RBD rbd; + ASSERT_EQ(0, rbd.group_create(ioctx, group_name)); + + ASSERT_EQ(0, rbd.group_image_add(ioctx, group_name, ioctx, + m_image_name.c_str())); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, m_image_name.c_str(), NULL)); + bufferlist expect_bl; + bufferlist read_bl; + expect_bl.append(std::string(512, '1')); + ASSERT_EQ((ssize_t)expect_bl.length(), image.write(0, expect_bl.length(), expect_bl)); + ASSERT_EQ(512, image.read(0, 512, read_bl)); + ASSERT_TRUE(expect_bl.contents_equal(read_bl)); + + ASSERT_EQ(0, rbd.group_snap_create(ioctx, group_name, snap_name)); + + std::vector<librbd::group_snap_info_t> snaps; + ASSERT_EQ(0, rbd.group_snap_list(ioctx, group_name, &snaps, + sizeof(librbd::group_snap_info_t))); + ASSERT_EQ(1U, snaps.size()); + + ASSERT_EQ(snap_name, snaps[0].name); + + bufferlist write_bl; + write_bl.append(std::string(1024, '2')); + ASSERT_EQ(1024, image.write(513, write_bl.length(), write_bl)); + + read_bl.clear(); + ASSERT_EQ(1024, image.read(513, 1024, read_bl)); + ASSERT_TRUE(write_bl.contents_equal(read_bl)); + + ASSERT_EQ(0, rbd.group_snap_rollback(ioctx, group_name, snap_name)); + + ASSERT_EQ(512, image.read(0, 512, read_bl)); + ASSERT_TRUE(expect_bl.contents_equal(read_bl)); + + ASSERT_EQ(0, image.close()); + + ASSERT_EQ(0, rbd.group_snap_remove(ioctx, group_name, snap_name)); + + snaps.clear(); + ASSERT_EQ(0, rbd.group_snap_list(ioctx, group_name, &snaps, + sizeof(librbd::group_snap_info_t))); + ASSERT_EQ(0U, snaps.size()); + + ASSERT_EQ(0, rbd.group_remove(ioctx, group_name)); +} diff --git a/src/test/librbd/test_ImageWatcher.cc b/src/test/librbd/test_ImageWatcher.cc new file mode 100644 index 00000000..9343f74d --- /dev/null +++ b/src/test/librbd/test_ImageWatcher.cc @@ -0,0 +1,697 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "include/int_types.h" +#include "include/stringify.h" +#include "include/rados/librados.h" +#include "include/rbd/librbd.hpp" +#include "common/Cond.h" +#include "common/errno.h" +#include "common/Mutex.h" +#include "common/RWLock.h" +#include "cls/lock/cls_lock_client.h" +#include "cls/lock/cls_lock_types.h" +#include "librbd/internal.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageWatcher.h" +#include "librbd/WatchNotifyTypes.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/io/ImageRequestWQ.h" +#include "test/librados/test.h" +#include "gtest/gtest.h" +#include <boost/assign/std/set.hpp> +#include <boost/assign/std/map.hpp> +#include <boost/bind.hpp> +#include <boost/scope_exit.hpp> +#include <boost/thread/thread.hpp> +#include <iostream> +#include <map> +#include <set> +#include <sstream> +#include <vector> + +using namespace ceph; +using namespace boost::assign; +using namespace librbd::watch_notify; + +void register_test_image_watcher() { +} + +class TestImageWatcher : public TestFixture { +public: + + TestImageWatcher() : m_watch_ctx(NULL), m_callback_lock("m_callback_lock") + { + } + + class WatchCtx : public librados::WatchCtx2 { + public: + explicit WatchCtx(TestImageWatcher &parent) : m_parent(parent), m_handle(0) {} + + int watch(const librbd::ImageCtx &ictx) { + m_header_oid = ictx.header_oid; + return m_parent.m_ioctx.watch2(m_header_oid, &m_handle, this); + } + + int unwatch() { + return m_parent.m_ioctx.unwatch2(m_handle); + } + + void handle_notify(uint64_t notify_id, + uint64_t cookie, + uint64_t notifier_id, + bufferlist& bl) override { + try { + int op; + bufferlist payload; + auto iter = bl.cbegin(); + DECODE_START(1, iter); + decode(op, iter); + iter.copy_all(payload); + DECODE_FINISH(iter); + + NotifyOp notify_op = static_cast<NotifyOp>(op); + /* + std::cout << "NOTIFY: " << notify_op << ", " << notify_id + << ", " << cookie << ", " << notifier_id << std::endl; + */ + + Mutex::Locker l(m_parent.m_callback_lock); + m_parent.m_notify_payloads[notify_op] = payload; + + bufferlist reply; + if (m_parent.m_notify_acks.count(notify_op) > 0) { + reply = m_parent.m_notify_acks[notify_op]; + m_parent.m_notifies += notify_op; + m_parent.m_callback_cond.Signal(); + } + + m_parent.m_ioctx.notify_ack(m_header_oid, notify_id, cookie, reply); + } catch (...) { + FAIL(); + } + } + + void handle_error(uint64_t cookie, int err) override { + std::cerr << "ERROR: " << cookie << ", " << cpp_strerror(err) + << std::endl; + } + + uint64_t get_handle() const { + return m_handle; + } + + private: + TestImageWatcher &m_parent; + std::string m_header_oid; + uint64_t m_handle; + }; + + void TearDown() override { + deregister_image_watch(); + TestFixture::TearDown(); + } + + int deregister_image_watch() { + if (m_watch_ctx != NULL) { + int r = m_watch_ctx->unwatch(); + + librados::Rados rados(m_ioctx); + rados.watch_flush(); + + delete m_watch_ctx; + m_watch_ctx = NULL; + return r; + } + return 0; + } + + int register_image_watch(librbd::ImageCtx &ictx) { + m_watch_ctx = new WatchCtx(*this); + return m_watch_ctx->watch(ictx); + } + + bool wait_for_notifies(librbd::ImageCtx &ictx) { + Mutex::Locker l(m_callback_lock); + while (m_notifies.size() < m_notify_acks.size()) { + int r = m_callback_cond.WaitInterval(m_callback_lock, + utime_t(10, 0)); + if (r != 0) { + break; + } + } + return (m_notifies.size() == m_notify_acks.size()); + } + + bufferlist create_response_message(int r) { + bufferlist bl; + encode(ResponseMessage(r), bl); + return bl; + } + + bool extract_async_request_id(NotifyOp op, AsyncRequestId *id) { + if (m_notify_payloads.count(op) == 0) { + return false; + } + + bufferlist payload = m_notify_payloads[op]; + auto iter = payload.cbegin(); + + switch (op) { + case NOTIFY_OP_FLATTEN: + { + FlattenPayload payload; + payload.decode(2, iter); + *id = payload.async_request_id; + } + return true; + case NOTIFY_OP_RESIZE: + { + ResizePayload payload; + payload.decode(2, iter); + *id = payload.async_request_id; + } + return true; + case NOTIFY_OP_REBUILD_OBJECT_MAP: + { + RebuildObjectMapPayload payload; + payload.decode(2, iter); + *id = payload.async_request_id; + } + return true; + default: + break; + } + return false; + } + + int notify_async_progress(librbd::ImageCtx *ictx, const AsyncRequestId &id, + uint64_t offset, uint64_t total) { + bufferlist bl; + encode(NotifyMessage(AsyncProgressPayload(id, offset, total)), bl); + return m_ioctx.notify2(ictx->header_oid, bl, 5000, NULL); + } + + int notify_async_complete(librbd::ImageCtx *ictx, const AsyncRequestId &id, + int r) { + bufferlist bl; + encode(NotifyMessage(AsyncCompletePayload(id, r)), bl); + return m_ioctx.notify2(ictx->header_oid, bl, 5000, NULL); + } + + typedef std::map<NotifyOp, bufferlist> NotifyOpPayloads; + typedef std::set<NotifyOp> NotifyOps; + + WatchCtx *m_watch_ctx; + + NotifyOps m_notifies; + NotifyOpPayloads m_notify_payloads; + NotifyOpPayloads m_notify_acks; + + AsyncRequestId m_async_request_id; + + Mutex m_callback_lock; + Cond m_callback_cond; + +}; + +struct ProgressContext : public librbd::ProgressContext { + Mutex mutex; + Cond cond; + bool received; + uint64_t offset; + uint64_t total; + + ProgressContext() : mutex("ProgressContext::mutex"), received(false), + offset(0), total(0) {} + + int update_progress(uint64_t offset_, uint64_t total_) override { + Mutex::Locker l(mutex); + offset = offset_; + total = total_; + received = true; + cond.Signal(); + return 0; + } + + bool wait(librbd::ImageCtx *ictx, uint64_t offset_, uint64_t total_) { + Mutex::Locker l(mutex); + while (!received) { + int r = cond.WaitInterval(mutex, utime_t(10, 0)); + if (r != 0) { + break; + } + } + return (received && offset == offset_ && total == total_); + } +}; + +struct FlattenTask { + librbd::ImageCtx *ictx; + ProgressContext *progress_context; + int result; + + FlattenTask(librbd::ImageCtx *ictx_, ProgressContext *ctx) + : ictx(ictx_), progress_context(ctx), result(0) {} + + void operator()() { + RWLock::RLocker l(ictx->owner_lock); + C_SaferCond ctx; + ictx->image_watcher->notify_flatten(0, *progress_context, &ctx); + result = ctx.wait(); + } +}; + +struct ResizeTask { + librbd::ImageCtx *ictx; + ProgressContext *progress_context; + int result; + + ResizeTask(librbd::ImageCtx *ictx_, ProgressContext *ctx) + : ictx(ictx_), progress_context(ctx), result(0) {} + + void operator()() { + RWLock::RLocker l(ictx->owner_lock); + C_SaferCond ctx; + ictx->image_watcher->notify_resize(0, 0, true, *progress_context, &ctx); + result = ctx.wait(); + } +}; + +struct RebuildObjectMapTask { + librbd::ImageCtx *ictx; + ProgressContext *progress_context; + int result; + + RebuildObjectMapTask(librbd::ImageCtx *ictx_, ProgressContext *ctx) + : ictx(ictx_), progress_context(ctx), result(0) {} + + void operator()() { + RWLock::RLocker l(ictx->owner_lock); + C_SaferCond ctx; + ictx->image_watcher->notify_rebuild_object_map(0, *progress_context, &ctx); + result = ctx.wait(); + } +}; + +TEST_F(TestImageWatcher, NotifyHeaderUpdate) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + + m_notify_acks = {{NOTIFY_OP_HEADER_UPDATE, {}}}; + ictx->notify_update(); + + ASSERT_TRUE(wait_for_notifies(*ictx)); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_HEADER_UPDATE; + ASSERT_EQ(expected_notify_ops, m_notifies); +} + +TEST_F(TestImageWatcher, NotifyFlatten) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_FLATTEN, create_response_message(0)}}; + + ProgressContext progress_context; + FlattenTask flatten_task(ictx, &progress_context); + boost::thread thread(boost::ref(flatten_task)); + + ASSERT_TRUE(wait_for_notifies(*ictx)); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_FLATTEN; + ASSERT_EQ(expected_notify_ops, m_notifies); + + AsyncRequestId async_request_id; + ASSERT_TRUE(extract_async_request_id(NOTIFY_OP_FLATTEN, &async_request_id)); + + ASSERT_EQ(0, notify_async_progress(ictx, async_request_id, 10, 20)); + ASSERT_TRUE(progress_context.wait(ictx, 10, 20)); + + ASSERT_EQ(0, notify_async_complete(ictx, async_request_id, 0)); + + ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10))); + ASSERT_EQ(0, flatten_task.result); +} + +TEST_F(TestImageWatcher, NotifyResize) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_RESIZE, create_response_message(0)}}; + + ProgressContext progress_context; + ResizeTask resize_task(ictx, &progress_context); + boost::thread thread(boost::ref(resize_task)); + + ASSERT_TRUE(wait_for_notifies(*ictx)); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_RESIZE; + ASSERT_EQ(expected_notify_ops, m_notifies); + + AsyncRequestId async_request_id; + ASSERT_TRUE(extract_async_request_id(NOTIFY_OP_RESIZE, &async_request_id)); + + ASSERT_EQ(0, notify_async_progress(ictx, async_request_id, 10, 20)); + ASSERT_TRUE(progress_context.wait(ictx, 10, 20)); + + ASSERT_EQ(0, notify_async_complete(ictx, async_request_id, 0)); + + ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10))); + ASSERT_EQ(0, resize_task.result); +} + +TEST_F(TestImageWatcher, NotifyRebuildObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_REBUILD_OBJECT_MAP, create_response_message(0)}}; + + ProgressContext progress_context; + RebuildObjectMapTask rebuild_task(ictx, &progress_context); + boost::thread thread(boost::ref(rebuild_task)); + + ASSERT_TRUE(wait_for_notifies(*ictx)); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_REBUILD_OBJECT_MAP; + ASSERT_EQ(expected_notify_ops, m_notifies); + + AsyncRequestId async_request_id; + ASSERT_TRUE(extract_async_request_id(NOTIFY_OP_REBUILD_OBJECT_MAP, + &async_request_id)); + + ASSERT_EQ(0, notify_async_progress(ictx, async_request_id, 10, 20)); + ASSERT_TRUE(progress_context.wait(ictx, 10, 20)); + + ASSERT_EQ(0, notify_async_complete(ictx, async_request_id, 0)); + + ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10))); + ASSERT_EQ(0, rebuild_task.result); +} + +TEST_F(TestImageWatcher, NotifySnapCreate) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_SNAP_CREATE, create_response_message(0)}}; + + RWLock::RLocker l(ictx->owner_lock); + C_SaferCond notify_ctx; + ictx->image_watcher->notify_snap_create(cls::rbd::UserSnapshotNamespace(), + "snap", ¬ify_ctx); + ASSERT_EQ(0, notify_ctx.wait()); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_SNAP_CREATE; + ASSERT_EQ(expected_notify_ops, m_notifies); +} + +TEST_F(TestImageWatcher, NotifySnapCreateError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_SNAP_CREATE, create_response_message(-EEXIST)}}; + + RWLock::RLocker l(ictx->owner_lock); + C_SaferCond notify_ctx; + ictx->image_watcher->notify_snap_create(cls::rbd::UserSnapshotNamespace(), + "snap", ¬ify_ctx); + ASSERT_EQ(-EEXIST, notify_ctx.wait()); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_SNAP_CREATE; + ASSERT_EQ(expected_notify_ops, m_notifies); +} + +TEST_F(TestImageWatcher, NotifySnapRename) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_SNAP_RENAME, create_response_message(0)}}; + + RWLock::RLocker l(ictx->owner_lock); + C_SaferCond notify_ctx; + ictx->image_watcher->notify_snap_rename(1, "snap-rename", ¬ify_ctx); + ASSERT_EQ(0, notify_ctx.wait()); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_SNAP_RENAME; + ASSERT_EQ(expected_notify_ops, m_notifies); +} + +TEST_F(TestImageWatcher, NotifySnapRenameError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_SNAP_RENAME, create_response_message(-EEXIST)}}; + + RWLock::RLocker l(ictx->owner_lock); + C_SaferCond notify_ctx; + ictx->image_watcher->notify_snap_rename(1, "snap-rename", ¬ify_ctx); + ASSERT_EQ(-EEXIST, notify_ctx.wait()); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_SNAP_RENAME; + ASSERT_EQ(expected_notify_ops, m_notifies); +} + +TEST_F(TestImageWatcher, NotifySnapRemove) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_SNAP_REMOVE, create_response_message(0)}}; + + RWLock::RLocker l(ictx->owner_lock); + C_SaferCond notify_ctx; + ictx->image_watcher->notify_snap_remove(cls::rbd::UserSnapshotNamespace(), + "snap", + ¬ify_ctx); + ASSERT_EQ(0, notify_ctx.wait()); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_SNAP_REMOVE; + ASSERT_EQ(expected_notify_ops, m_notifies); +} + +TEST_F(TestImageWatcher, NotifySnapProtect) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_SNAP_PROTECT, create_response_message(0)}}; + + RWLock::RLocker l(ictx->owner_lock); + C_SaferCond notify_ctx; + ictx->image_watcher->notify_snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap", + ¬ify_ctx); + ASSERT_EQ(0, notify_ctx.wait()); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_SNAP_PROTECT; + ASSERT_EQ(expected_notify_ops, m_notifies); +} + +TEST_F(TestImageWatcher, NotifySnapUnprotect) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_SNAP_UNPROTECT, create_response_message(0)}}; + + RWLock::RLocker l(ictx->owner_lock); + C_SaferCond notify_ctx; + ictx->image_watcher->notify_snap_unprotect(cls::rbd::UserSnapshotNamespace(), + "snap", + ¬ify_ctx); + ASSERT_EQ(0, notify_ctx.wait()); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_SNAP_UNPROTECT; + ASSERT_EQ(expected_notify_ops, m_notifies); +} + +TEST_F(TestImageWatcher, NotifyRename) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_RENAME, create_response_message(0)}}; + + RWLock::RLocker l(ictx->owner_lock); + C_SaferCond notify_ctx; + ictx->image_watcher->notify_rename("new_name", ¬ify_ctx); + ASSERT_EQ(0, notify_ctx.wait()); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_RENAME; + ASSERT_EQ(expected_notify_ops, m_notifies); +} + +TEST_F(TestImageWatcher, NotifyAsyncTimedOut) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_FLATTEN, {}}}; + + ProgressContext progress_context; + FlattenTask flatten_task(ictx, &progress_context); + boost::thread thread(boost::ref(flatten_task)); + + ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10))); + ASSERT_EQ(-ETIMEDOUT, flatten_task.result); +} + +TEST_F(TestImageWatcher, NotifyAsyncError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_FLATTEN, create_response_message(-EIO)}}; + + ProgressContext progress_context; + FlattenTask flatten_task(ictx, &progress_context); + boost::thread thread(boost::ref(flatten_task)); + + ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10))); + ASSERT_EQ(-EIO, flatten_task.result); +} + +TEST_F(TestImageWatcher, NotifyAsyncCompleteError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_FLATTEN, create_response_message(0)}}; + + ProgressContext progress_context; + FlattenTask flatten_task(ictx, &progress_context); + boost::thread thread(boost::ref(flatten_task)); + + ASSERT_TRUE(wait_for_notifies(*ictx)); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_FLATTEN; + ASSERT_EQ(expected_notify_ops, m_notifies); + + AsyncRequestId async_request_id; + ASSERT_TRUE(extract_async_request_id(NOTIFY_OP_FLATTEN, &async_request_id)); + + ASSERT_EQ(0, notify_async_complete(ictx, async_request_id, -ESHUTDOWN)); + + ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10))); + ASSERT_EQ(-ESHUTDOWN, flatten_task.result); +} + +TEST_F(TestImageWatcher, NotifyAsyncRequestTimedOut) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ictx->config.set_val("rbd_request_timed_out_seconds", "0"); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, + "auto " + stringify(m_watch_ctx->get_handle()))); + + m_notify_acks = {{NOTIFY_OP_FLATTEN, create_response_message(0)}}; + + ProgressContext progress_context; + FlattenTask flatten_task(ictx, &progress_context); + boost::thread thread(boost::ref(flatten_task)); + + ASSERT_TRUE(wait_for_notifies(*ictx)); + + ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10))); + ASSERT_EQ(-ETIMEDOUT, flatten_task.result); +} + diff --git a/src/test/librbd/test_Migration.cc b/src/test/librbd/test_Migration.cc new file mode 100644 index 00000000..c50996f9 --- /dev/null +++ b/src/test/librbd/test_Migration.cc @@ -0,0 +1,1350 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librados/test.h" +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/api/Group.h" +#include "librbd/api/Image.h" +#include "librbd/api/Migration.h" +#include "librbd/api/Mirror.h" +#include "librbd/api/Namespace.h" +#include "librbd/image/AttachChildRequest.h" +#include "librbd/image/AttachParentRequest.h" +#include "librbd/internal.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/io/ReadResult.h" + +#include <boost/scope_exit.hpp> + +void register_test_migration() { +} + +struct TestMigration : public TestFixture { + static void SetUpTestCase() { + TestFixture::SetUpTestCase(); + + _other_pool_name = get_temp_pool_name("test-librbd-"); + ASSERT_EQ(0, _rados.pool_create(_other_pool_name.c_str())); + } + + static void TearDownTestCase() { + ASSERT_EQ(0, _rados.pool_delete(_other_pool_name.c_str())); + + TestFixture::TearDownTestCase(); + } + + void SetUp() override { + TestFixture::SetUp(); + + ASSERT_EQ(0, _rados.ioctx_create(_other_pool_name.c_str(), + _other_pool_ioctx)); + + open_image(m_ioctx, m_image_name, &m_ictx); + m_image_id = m_ictx->id; + + std::string ref_image_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(m_rbd, m_ioctx, ref_image_name, m_ictx->size)); + EXPECT_EQ(0, _rados.ioctx_create2(m_ioctx.get_id(), m_ref_ioctx)); + open_image(m_ref_ioctx, ref_image_name, &m_ref_ictx); + + resize(20 * (1 << 22)); + } + + void TearDown() override { + if (m_ref_ictx != nullptr) { + close_image(m_ref_ictx); + } + if (m_ictx != nullptr) { + close_image(m_ictx); + } + + _other_pool_ioctx.close(); + + TestFixture::TearDown(); + } + + void compare(const std::string &description = "") { + vector<librbd::snap_info_t> src_snaps, dst_snaps; + + EXPECT_EQ(m_ref_ictx->size, m_ictx->size); + EXPECT_EQ(0, librbd::snap_list(m_ref_ictx, src_snaps)); + EXPECT_EQ(0, librbd::snap_list(m_ictx, dst_snaps)); + EXPECT_EQ(src_snaps.size(), dst_snaps.size()); + for (size_t i = 0; i <= src_snaps.size(); i++) { + const char *src_snap_name = nullptr; + const char *dst_snap_name = nullptr; + if (i < src_snaps.size()) { + EXPECT_EQ(src_snaps[i].name, dst_snaps[i].name); + src_snap_name = src_snaps[i].name.c_str(); + dst_snap_name = dst_snaps[i].name.c_str(); + } + EXPECT_EQ(0, librbd::api::Image<>::snap_set( + m_ref_ictx, cls::rbd::UserSnapshotNamespace(), + src_snap_name)); + EXPECT_EQ(0, librbd::api::Image<>::snap_set( + m_ictx, cls::rbd::UserSnapshotNamespace(), + dst_snap_name)); + compare_snaps( + description + " snap: " + (src_snap_name ? src_snap_name : "null"), + m_ref_ictx, m_ictx); + } + } + + void compare_snaps(const std::string &description, librbd::ImageCtx *src_ictx, + librbd::ImageCtx *dst_ictx) { + uint64_t src_size, dst_size; + { + RWLock::RLocker src_locker(src_ictx->snap_lock); + RWLock::RLocker dst_locker(dst_ictx->snap_lock); + src_size = src_ictx->get_image_size(src_ictx->snap_id); + dst_size = dst_ictx->get_image_size(dst_ictx->snap_id); + } + if (src_size != dst_size) { + std::cout << description << ": size differs" << std::endl; + EXPECT_EQ(src_size, dst_size); + } + + if (dst_ictx->test_features(RBD_FEATURE_LAYERING)) { + bool flags_set; + RWLock::RLocker dst_locker(dst_ictx->snap_lock); + EXPECT_EQ(0, dst_ictx->test_flags(dst_ictx->snap_id, + RBD_FLAG_OBJECT_MAP_INVALID, + dst_ictx->snap_lock, &flags_set)); + EXPECT_FALSE(flags_set); + } + + ssize_t read_size = 1 << src_ictx->order; + uint64_t offset = 0; + while (offset < src_size) { + read_size = std::min(read_size, static_cast<ssize_t>(src_size - offset)); + + bufferptr src_ptr(read_size); + bufferlist src_bl; + src_bl.push_back(src_ptr); + librbd::io::ReadResult src_result{&src_bl}; + EXPECT_EQ(read_size, src_ictx->io_work_queue->read( + offset, read_size, librbd::io::ReadResult{src_result}, 0)); + + bufferptr dst_ptr(read_size); + bufferlist dst_bl; + dst_bl.push_back(dst_ptr); + librbd::io::ReadResult dst_result{&dst_bl}; + EXPECT_EQ(read_size, dst_ictx->io_work_queue->read( + offset, read_size, librbd::io::ReadResult{dst_result}, 0)); + + if (!src_bl.contents_equal(dst_bl)) { + std::cout << description + << ", block " << offset << "~" << read_size << " differs" + << std::endl; + char *c = getenv("TEST_RBD_MIGRATION_VERBOSE"); + if (c != NULL && *c != '\0') { + std::cout << "src block: " << std::endl; src_bl.hexdump(std::cout); + std::cout << "dst block: " << std::endl; dst_bl.hexdump(std::cout); + } + } + EXPECT_TRUE(src_bl.contents_equal(dst_bl)); + offset += read_size; + } + } + + void open_image(librados::IoCtx& io_ctx, const std::string &name, + const std::string &id, bool read_only, int flags, + librbd::ImageCtx **ictx) { + *ictx = new librbd::ImageCtx(name, id, nullptr, io_ctx, read_only); + m_ictxs.insert(*ictx); + + ASSERT_EQ(0, (*ictx)->state->open(flags)); + (*ictx)->discard_granularity_bytes = 0; + } + + void open_image(librados::IoCtx& io_ctx, const std::string &name, + librbd::ImageCtx **ictx) { + open_image(io_ctx, name, "", false, 0, ictx); + } + + void migration_prepare(librados::IoCtx& dst_io_ctx, + const std::string &dst_name, int r = 0) { + std::cout << __func__ << std::endl; + + close_image(m_ictx); + m_ictx = nullptr; + + EXPECT_EQ(r, librbd::api::Migration<>::prepare(m_ioctx, m_image_name, + dst_io_ctx, dst_name, + m_opts)); + if (r == 0) { + open_image(dst_io_ctx, dst_name, &m_ictx); + } else { + open_image(m_ioctx, m_image_name, &m_ictx); + } + compare("after prepare"); + } + + void migration_execute(librados::IoCtx& io_ctx, const std::string &name, + int r = 0) { + std::cout << __func__ << std::endl; + + librbd::NoOpProgressContext no_op; + EXPECT_EQ(r, librbd::api::Migration<>::execute(io_ctx, name, no_op)); + } + + void migration_abort(librados::IoCtx& io_ctx, const std::string &name, + int r = 0) { + std::cout << __func__ << std::endl; + + std::string dst_name = m_ictx->name; + close_image(m_ictx); + m_ictx = nullptr; + + librbd::NoOpProgressContext no_op; + EXPECT_EQ(r, librbd::api::Migration<>::abort(io_ctx, name, no_op)); + + if (r == 0) { + open_image(m_ioctx, m_image_name, &m_ictx); + } else { + open_image(m_ioctx, dst_name, &m_ictx); + } + + compare("after abort"); + } + + void migration_commit(librados::IoCtx& io_ctx, const std::string &name) { + std::cout << __func__ << std::endl; + + librbd::NoOpProgressContext no_op; + EXPECT_EQ(0, librbd::api::Migration<>::commit(io_ctx, name, no_op)); + + compare("after commit"); + } + + void migration_status(librbd::image_migration_state_t state) { + librbd::image_migration_status_t status; + EXPECT_EQ(0, librbd::api::Migration<>::status(m_ioctx, m_image_name, + &status)); + EXPECT_EQ(status.source_pool_id, m_ioctx.get_id()); + EXPECT_EQ(status.source_pool_namespace, m_ioctx.get_namespace()); + EXPECT_EQ(status.source_image_name, m_image_name); + EXPECT_EQ(status.source_image_id, m_image_id); + EXPECT_EQ(status.dest_pool_id, m_ictx->md_ctx.get_id()); + EXPECT_EQ(status.dest_pool_namespace, m_ictx->md_ctx.get_namespace()); + EXPECT_EQ(status.dest_image_name, m_ictx->name); + EXPECT_EQ(status.dest_image_id, m_ictx->id); + EXPECT_EQ(status.state, state); + } + + void migrate(librados::IoCtx& dst_io_ctx, const std::string &dst_name) { + migration_prepare(dst_io_ctx, dst_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + migration_execute(dst_io_ctx, dst_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(dst_io_ctx, dst_name); + } + + void write(uint64_t off, uint64_t len, char c) { + std::cout << "write: " << c << " " << off << "~" << len << std::endl; + + bufferlist ref_bl; + ref_bl.append(std::string(len, c)); + ASSERT_EQ(static_cast<ssize_t>(len), + m_ref_ictx->io_work_queue->write(off, len, std::move(ref_bl), 0)); + bufferlist bl; + bl.append(std::string(len, c)); + ASSERT_EQ(static_cast<ssize_t>(len), + m_ictx->io_work_queue->write(off, len, std::move(bl), 0)); + } + + void discard(uint64_t off, uint64_t len) { + std::cout << "discard: " << off << "~" << len << std::endl; + + ASSERT_EQ(static_cast<ssize_t>(len), + m_ref_ictx->io_work_queue->discard(off, len, false)); + ASSERT_EQ(static_cast<ssize_t>(len), + m_ictx->io_work_queue->discard(off, len, false)); + } + + void flush() { + ASSERT_EQ(0, m_ref_ictx->io_work_queue->flush()); + ASSERT_EQ(0, m_ictx->io_work_queue->flush()); + } + + void snap_create(const std::string &snap_name) { + std::cout << "snap_create: " << snap_name << std::endl; + + flush(); + + ASSERT_EQ(0, TestFixture::snap_create(*m_ref_ictx, snap_name)); + ASSERT_EQ(0, TestFixture::snap_create(*m_ictx, snap_name)); + } + + void snap_protect(const std::string &snap_name) { + std::cout << "snap_protect: " << snap_name << std::endl; + + ASSERT_EQ(0, TestFixture::snap_protect(*m_ref_ictx, snap_name)); + ASSERT_EQ(0, TestFixture::snap_protect(*m_ictx, snap_name)); + } + + void clone(const std::string &snap_name) { + snap_protect(snap_name); + + int order = m_ref_ictx->order; + uint64_t features; + ASSERT_EQ(0, librbd::get_features(m_ref_ictx, &features)); + + std::string ref_clone_name = get_temp_image_name(); + std::string clone_name = get_temp_image_name(); + + std::cout << "clone " << m_ictx->name << " -> " << clone_name + << std::endl; + + ASSERT_EQ(0, librbd::clone(m_ref_ictx->md_ctx, m_ref_ictx->name.c_str(), + snap_name.c_str(), m_ref_ioctx, + ref_clone_name.c_str(), features, &order, + m_ref_ictx->stripe_unit, + m_ref_ictx->stripe_count)); + + ASSERT_EQ(0, librbd::clone(m_ictx->md_ctx, m_ictx->name.c_str(), + snap_name.c_str(), m_ioctx, + clone_name.c_str(), features, &order, + m_ictx->stripe_unit, + m_ictx->stripe_count)); + + close_image(m_ref_ictx); + open_image(m_ref_ioctx, ref_clone_name, &m_ref_ictx); + + close_image(m_ictx); + open_image(m_ioctx, clone_name, &m_ictx); + m_image_name = m_ictx->name; + m_image_id = m_ictx->id; + } + + void resize(uint64_t size) { + std::cout << "resize: " << size << std::endl; + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, m_ref_ictx->operations->resize(size, true, no_op)); + ASSERT_EQ(0, m_ictx->operations->resize(size, true, no_op)); + } + + void test_no_snaps() { + uint64_t len = (1 << m_ictx->order) * 2 + 1; + write(0 * len, len, '1'); + write(2 * len, len, '1'); + flush(); + } + + void test_snaps() { + uint64_t len = (1 << m_ictx->order) * 2 + 1; + write(0 * len, len, '1'); + snap_create("snap1"); + write(1 * len, len, '1'); + + write(0 * len, 1000, 'X'); + discard(1000 + 10, 1000); + + snap_create("snap2"); + + write(1 * len, 1000, 'X'); + discard(2 * len + 10, 1000); + + uint64_t size = m_ictx->size; + + resize(size << 1); + + write(size - 1, len, '2'); + + snap_create("snap3"); + + resize(size); + + discard(size - 1, 1); + + flush(); + } + + void test_clone() { + uint64_t len = (1 << m_ictx->order) * 2 + 1; + write(0 * len, len, 'X'); + write(2 * len, len, 'X'); + + snap_create("snap"); + clone("snap"); + + write(0, 1000, 'X'); + discard(1010, 1000); + + snap_create("snap"); + clone("snap"); + + write(1000, 1000, 'X'); + discard(2010, 1000); + + flush(); + } + + template <typename L> + void test_migrate_parent(uint32_t clone_format, L&& test) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + std::string prev_clone_format; + ASSERT_EQ(0, _rados.conf_get("rbd_default_clone_format", + prev_clone_format)); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", + stringify(clone_format).c_str())); + BOOST_SCOPE_EXIT_TPL(&prev_clone_format) { + _rados.conf_set("rbd_default_clone_format", prev_clone_format.c_str()); + } BOOST_SCOPE_EXIT_END; + + write(0, 10, 'A'); + snap_create("snap1"); + snap_protect("snap1"); + + int order = m_ictx->order; + uint64_t features; + ASSERT_EQ(0, librbd::get_features(m_ictx, &features)); + + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ictx->md_ctx, m_ictx->name.c_str(), "snap1", + m_ioctx, clone_name.c_str(), features, &order, + m_ictx->stripe_unit, m_ictx->stripe_count)); + + librbd::ImageCtx *child_ictx; + open_image(m_ioctx, clone_name, &child_ictx); + + test(child_ictx); + + ASSERT_EQ(0, child_ictx->state->refresh()); + + bufferlist bl; + bufferptr ptr(10); + bl.push_back(ptr); + librbd::io::ReadResult result{&bl}; + ASSERT_EQ(10, child_ictx->io_work_queue->read( + 0, 10, librbd::io::ReadResult{result}, 0)); + bufferlist ref_bl; + ref_bl.append(std::string(10, 'A')); + ASSERT_TRUE(ref_bl.contents_equal(bl)); + close_image(child_ictx); + } + + void test_stress(const std::string &snap_name_prefix = "snap", + char start_char = 'A') { + uint64_t initial_size = m_ictx->size; + + int nsnaps = 4; + const char *c = getenv("TEST_RBD_MIGRATION_STRESS_NSNAPS"); + if (c != NULL) { + std::stringstream ss(c); + ASSERT_TRUE(ss >> nsnaps); + } + + int nwrites = 4; + c = getenv("TEST_RBD_MIGRATION_STRESS_NWRITES"); + if (c != NULL) { + std::stringstream ss(c); + ASSERT_TRUE(ss >> nwrites); + } + + for (int i = 0; i < nsnaps; i++) { + for (int j = 0; j < nwrites; j++) { + size_t len = rand() % ((1 << m_ictx->order) * 2); + ASSERT_GT(m_ictx->size, len); + uint64_t off = std::min(static_cast<uint64_t>(rand() % m_ictx->size), + static_cast<uint64_t>(m_ictx->size - len)); + write(off, len, start_char + i); + + len = rand() % ((1 << m_ictx->order) * 2); + ASSERT_GT(m_ictx->size, len); + off = std::min(static_cast<uint64_t>(rand() % m_ictx->size), + static_cast<uint64_t>(m_ictx->size - len)); + discard(off, len); + } + + std::string snap_name = snap_name_prefix + stringify(i); + snap_create(snap_name); + + if (m_ictx->test_features(RBD_FEATURE_LAYERING) && + !m_ictx->test_features(RBD_FEATURE_MIGRATING) && + rand() % 4) { + clone(snap_name); + } + + if (rand() % 2) { + librbd::NoOpProgressContext no_op; + uint64_t new_size = initial_size + rand() % m_ictx->size; + resize(new_size); + ASSERT_EQ(new_size, m_ictx->size); + } + } + flush(); + } + + void test_stress2(bool concurrent) { + test_stress(); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + thread user([this]() { + test_stress("user", 'a'); + for (int i = 0; i < 5; i++) { + uint64_t off = (i + 1) * m_ictx->size / 10; + uint64_t len = m_ictx->size / 40; + write(off, len, '1' + i); + + off += len / 4; + len /= 2; + discard(off, len); + } + flush(); + }); + + if (concurrent) { + librados::IoCtx io_ctx; + EXPECT_EQ(0, _rados.ioctx_create2(m_ioctx.get_id(), io_ctx)); + migration_execute(io_ctx, m_image_name); + io_ctx.close(); + user.join(); + } else { + user.join(); + compare("before execute"); + migration_execute(m_ioctx, m_image_name); + } + + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(m_ioctx, m_image_name); + } + + static std::string _other_pool_name; + static librados::IoCtx _other_pool_ioctx; + + std::string m_image_id; + librbd::ImageCtx *m_ictx = nullptr; + librados::IoCtx m_ref_ioctx; + librbd::ImageCtx *m_ref_ictx = nullptr; + librbd::ImageOptions m_opts; +}; + +std::string TestMigration::_other_pool_name; +librados::IoCtx TestMigration::_other_pool_ioctx; + +TEST_F(TestMigration, Empty) +{ + uint64_t features = m_ictx->features ^ RBD_FEATURE_LAYERING; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_FEATURES, features)); + + migrate(m_ioctx, m_image_name); + + ASSERT_EQ(features, m_ictx->features); +} + +TEST_F(TestMigration, OtherName) +{ + std::string name = get_temp_image_name(); + + migrate(m_ioctx, name); + + ASSERT_EQ(name, m_ictx->name); +} + +TEST_F(TestMigration, OtherPool) +{ + migrate(_other_pool_ioctx, m_image_name); + + ASSERT_EQ(_other_pool_ioctx.get_id(), m_ictx->md_ctx.get_id()); +} + +TEST_F(TestMigration, OtherNamespace) +{ + ASSERT_EQ(0, librbd::api::Namespace<>::create(_other_pool_ioctx, "ns1")); + _other_pool_ioctx.set_namespace("ns1"); + + migrate(_other_pool_ioctx, m_image_name); + + ASSERT_EQ(_other_pool_ioctx.get_id(), m_ictx->md_ctx.get_id()); + ASSERT_EQ(_other_pool_ioctx.get_namespace(), m_ictx->md_ctx.get_namespace()); + _other_pool_ioctx.set_namespace(""); +} + +TEST_F(TestMigration, DataPool) +{ + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_DATA_POOL, + _other_pool_ioctx.get_pool_name().c_str())); + + migrate(m_ioctx, m_image_name); + + ASSERT_EQ(_other_pool_ioctx.get_id(), m_ictx->data_ctx.get_id()); +} + +TEST_F(TestMigration, AbortAfterPrepare) +{ + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + migration_abort(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, AbortAfterFailedPrepare) +{ + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_DATA_POOL, "INVALID_POOL")); + + migration_prepare(m_ioctx, m_image_name, -ENOENT); + + // Migration is automatically aborted if prepare failed +} + +TEST_F(TestMigration, AbortAfterExecute) +{ + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + migration_execute(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_abort(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, OtherPoolAbortAfterExecute) +{ + migration_prepare(_other_pool_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + migration_execute(_other_pool_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_abort(_other_pool_ioctx, m_image_name); +} + +TEST_F(TestMigration, OtherNamespaceAbortAfterExecute) +{ + ASSERT_EQ(0, librbd::api::Namespace<>::create(_other_pool_ioctx, "ns2")); + _other_pool_ioctx.set_namespace("ns2"); + + migration_prepare(_other_pool_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + migration_execute(_other_pool_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_abort(_other_pool_ioctx, m_image_name); + + _other_pool_ioctx.set_namespace(""); + ASSERT_EQ(0, librbd::api::Namespace<>::remove(_other_pool_ioctx, "ns2")); +} + +TEST_F(TestMigration, MirroringSamePool) +{ + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + ASSERT_EQ(0, librbd::api::Mirror<>::mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE)); + + ASSERT_EQ(0, librbd::api::Mirror<>::image_enable(m_ictx, false)); + librbd::mirror_image_info_t info; + ASSERT_EQ(0, librbd::api::Mirror<>::image_get_info(m_ictx, &info)); + ASSERT_EQ(RBD_MIRROR_IMAGE_ENABLED, info.state); + + migrate(m_ioctx, m_image_name); + + ASSERT_EQ(0, librbd::api::Mirror<>::image_get_info(m_ictx, &info)); + ASSERT_EQ(RBD_MIRROR_IMAGE_ENABLED, info.state); +} + +TEST_F(TestMigration, MirroringAbort) +{ + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + ASSERT_EQ(0, librbd::api::Mirror<>::mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE)); + + ASSERT_EQ(0, librbd::api::Mirror<>::image_enable(m_ictx, false)); + librbd::mirror_image_info_t info; + ASSERT_EQ(0, librbd::api::Mirror<>::image_get_info(m_ictx, &info)); + ASSERT_EQ(RBD_MIRROR_IMAGE_ENABLED, info.state); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + ASSERT_EQ(0, librbd::api::Mirror<>::image_get_info(m_ictx, &info)); + ASSERT_EQ(RBD_MIRROR_IMAGE_DISABLED, info.state); + + migration_abort(m_ioctx, m_image_name); + + ASSERT_EQ(0, librbd::api::Mirror<>::image_get_info(m_ictx, &info)); + ASSERT_EQ(RBD_MIRROR_IMAGE_ENABLED, info.state); +} + +TEST_F(TestMigration, MirroringOtherPoolDisabled) +{ + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + ASSERT_EQ(0, librbd::api::Mirror<>::mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE)); + + ASSERT_EQ(0, librbd::api::Mirror<>::image_enable(m_ictx, false)); + librbd::mirror_image_info_t info; + ASSERT_EQ(0, librbd::api::Mirror<>::image_get_info(m_ictx, &info)); + ASSERT_EQ(RBD_MIRROR_IMAGE_ENABLED, info.state); + + migrate(_other_pool_ioctx, m_image_name); + + ASSERT_EQ(0, librbd::api::Mirror<>::image_get_info(m_ictx, &info)); + ASSERT_EQ(RBD_MIRROR_IMAGE_DISABLED, info.state); +} + +TEST_F(TestMigration, MirroringOtherPoolEnabled) +{ + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + ASSERT_EQ(0, librbd::api::Mirror<>::mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE)); + ASSERT_EQ(0, librbd::api::Mirror<>::mode_set(_other_pool_ioctx, + RBD_MIRROR_MODE_IMAGE)); + + ASSERT_EQ(0, librbd::api::Mirror<>::image_enable(m_ictx, false)); + librbd::mirror_image_info_t info; + ASSERT_EQ(0, librbd::api::Mirror<>::image_get_info(m_ictx, &info)); + ASSERT_EQ(RBD_MIRROR_IMAGE_ENABLED, info.state); + + migrate(_other_pool_ioctx, m_image_name); + + ASSERT_EQ(0, librbd::api::Mirror<>::image_get_info(m_ictx, &info)); + ASSERT_EQ(RBD_MIRROR_IMAGE_ENABLED, info.state); +} + +TEST_F(TestMigration, MirroringPool) +{ + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + ASSERT_EQ(0, librbd::api::Mirror<>::mode_set(_other_pool_ioctx, + RBD_MIRROR_MODE_POOL)); + librbd::mirror_image_info_t info; + ASSERT_EQ(0, librbd::api::Mirror<>::image_get_info(m_ictx, &info)); + ASSERT_EQ(RBD_MIRROR_IMAGE_DISABLED, info.state); + + migrate(_other_pool_ioctx, m_image_name); + + ASSERT_EQ(0, librbd::api::Mirror<>::image_get_info(m_ictx, &info)); + ASSERT_EQ(RBD_MIRROR_IMAGE_ENABLED, info.state); +} + +TEST_F(TestMigration, Group) +{ + REQUIRE_FORMAT_V2(); + + ASSERT_EQ(0, librbd::api::Group<>::create(m_ioctx, "123")); + ASSERT_EQ(0, librbd::api::Group<>::image_add(m_ioctx, "123", m_ioctx, + m_image_name.c_str())); + librbd::group_info_t info; + ASSERT_EQ(0, librbd::api::Group<>::image_get_group(m_ictx, &info)); + + std::string name = get_temp_image_name(); + + migrate(m_ioctx, name); + + ASSERT_EQ(0, librbd::api::Group<>::image_get_group(m_ictx, &info)); + ASSERT_EQ(info.name, "123"); + + ASSERT_EQ(0, librbd::api::Group<>::image_remove(m_ioctx, "123", m_ioctx, + name.c_str())); + ASSERT_EQ(0, librbd::api::Group<>::remove(m_ioctx, "123")); +} + +TEST_F(TestMigration, GroupAbort) +{ + REQUIRE_FORMAT_V2(); + + ASSERT_EQ(0, librbd::api::Group<>::create(m_ioctx, "123")); + ASSERT_EQ(0, librbd::api::Group<>::image_add(m_ioctx, "123", m_ioctx, + m_image_name.c_str())); + librbd::group_info_t info; + ASSERT_EQ(0, librbd::api::Group<>::image_get_group(m_ictx, &info)); + + std::string name = get_temp_image_name(); + + migration_prepare(m_ioctx, name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + ASSERT_EQ(0, librbd::api::Group<>::image_get_group(m_ictx, &info)); + ASSERT_EQ(info.name, "123"); + + migration_abort(m_ioctx, m_image_name); + + ASSERT_EQ(0, librbd::api::Group<>::image_get_group(m_ictx, &info)); + ASSERT_EQ(info.name, "123"); + + ASSERT_EQ(0, librbd::api::Group<>::image_remove(m_ioctx, "123", m_ioctx, + m_image_name.c_str())); + ASSERT_EQ(0, librbd::api::Group<>::remove(m_ioctx, "123")); +} + +TEST_F(TestMigration, NoSnaps) +{ + test_no_snaps(); + migrate(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, NoSnapsOtherPool) +{ + test_no_snaps(); + + test_no_snaps(); + migrate(_other_pool_ioctx, m_image_name); +} + +TEST_F(TestMigration, NoSnapsDataPool) +{ + test_no_snaps(); + + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_DATA_POOL, + _other_pool_ioctx.get_pool_name().c_str())); + migrate(m_ioctx, m_image_name); + + EXPECT_EQ(_other_pool_ioctx.get_id(), m_ictx->data_ctx.get_id()); +} + +TEST_F(TestMigration, NoSnapsShrinkAfterPrepare) +{ + test_no_snaps(); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + resize(m_ictx->size >> 1); + + migration_execute(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, NoSnapsShrinkToZeroBeforePrepare) +{ + test_no_snaps(); + resize(0); + + migrate(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, NoSnapsShrinkToZeroAfterPrepare) +{ + test_no_snaps(); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + resize(0); + + migration_execute(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, NoSnapsExpandAfterPrepare) +{ + test_no_snaps(); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + resize(m_ictx->size << 1); + + migration_execute(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, NoSnapsSnapAfterPrepare) +{ + test_no_snaps(); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + snap_create("after_prepare_snap"); + resize(m_ictx->size >> 1); + write(0, 1000, '*'); + + migration_execute(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, Snaps) +{ + test_snaps(); + migrate(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, SnapsOtherPool) +{ + test_snaps(); + + test_no_snaps(); + migrate(_other_pool_ioctx, m_image_name); + + EXPECT_EQ(_other_pool_ioctx.get_id(), m_ictx->md_ctx.get_id()); +} + +TEST_F(TestMigration, SnapsDataPool) +{ + test_snaps(); + + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_DATA_POOL, + _other_pool_ioctx.get_pool_name().c_str())); + migrate(m_ioctx, m_image_name); + + EXPECT_EQ(_other_pool_ioctx.get_id(), m_ictx->data_ctx.get_id()); +} + +TEST_F(TestMigration, SnapsShrinkAfterPrepare) +{ + test_snaps(); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + resize(m_ictx->size >> 1); + + migration_execute(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, SnapsShrinkToZeroBeforePrepare) +{ + test_snaps(); + resize(0); + + migrate(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, SnapsShrinkToZeroAfterPrepare) +{ + test_snaps(); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + resize(0); + + migration_execute(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, SnapsExpandAfterPrepare) +{ + test_snaps(); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + auto size = m_ictx->size; + resize(size << 1); + write(size, 1000, '*'); + + migration_execute(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, SnapsExpandAfterPrepare2) +{ + auto size = m_ictx->size; + + write(size >> 1, 10, 'X'); + snap_create("snap1"); + resize(size >> 1); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + resize(size); + write(size >> 1, 5, 'Y'); + + compare("before execute"); + + migration_execute(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, SnapsSnapAfterPrepare) +{ + test_snaps(); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + auto ictx = new librbd::ImageCtx(m_ictx->name.c_str(), "", "snap3", m_ioctx, + false); + ASSERT_EQ(0, ictx->state->open(0)); + EXPECT_EQ(0, librbd::api::Image<>::snap_set( + m_ref_ictx, cls::rbd::UserSnapshotNamespace(), "snap3")); + compare_snaps("opened after prepare snap3", m_ref_ictx, ictx); + EXPECT_EQ(0, librbd::api::Image<>::snap_set( + m_ref_ictx, cls::rbd::UserSnapshotNamespace(), nullptr)); + EXPECT_EQ(0, ictx->state->close()); + + snap_create("after_prepare_snap"); + resize(m_ictx->size >> 1); + write(0, 1000, '*'); + + migration_execute(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, SnapsSnapExpandAfterPrepare) +{ + test_snaps(); + + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + snap_create("after_prepare_snap"); + auto size = m_ictx->size; + resize(size << 1); + write(size, 1000, '*'); + + migration_execute(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_EXECUTED); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, Clone) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + test_clone(); + migrate(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, CloneParent) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + snap_create("snap"); + + librbd::linked_image_spec_t expected_parent_image; + expected_parent_image.image_id = m_ictx->id; + expected_parent_image.image_name = m_ictx->name; + + auto it = m_ictx->snap_ids.find({cls::rbd::UserSnapshotNamespace{}, "snap"}); + ASSERT_TRUE(it != m_ictx->snap_ids.end()); + + librbd::snap_spec_t expected_parent_snap; + expected_parent_snap.id = it->second; + + clone("snap"); + migration_prepare(m_ioctx, m_image_name); + + librbd::linked_image_spec_t parent_image; + librbd::snap_spec_t parent_snap; + ASSERT_EQ(0, librbd::api::Image<>::get_parent(m_ictx, &parent_image, + &parent_snap)); + ASSERT_EQ(expected_parent_image.image_id, parent_image.image_id); + ASSERT_EQ(expected_parent_image.image_name, parent_image.image_name); + ASSERT_EQ(expected_parent_snap.id, parent_snap.id); + + migration_abort(m_ioctx, m_image_name); +} + + +TEST_F(TestMigration, CloneUpdateAfterPrepare) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + write(0, 10, 'X'); + snap_create("snap"); + clone("snap"); + + migration_prepare(m_ioctx, m_image_name); + + write(0, 1, 'Y'); + + migration_execute(m_ioctx, m_image_name); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, TriggerAssertSnapcSeq) +{ + auto size = m_ictx->size; + + write((size >> 1) + 0, 10, 'A'); + snap_create("snap1"); + write((size >> 1) + 1, 10, 'B'); + + migration_prepare(m_ioctx, m_image_name); + + // copyup => deep copy (first time) + write((size >> 1) + 2, 10, 'C'); + + // preserve data before resizing + snap_create("snap2"); + + // decrease head overlap + resize(size >> 1); + + // migrate object => deep copy (second time) => assert_snapc_seq => -ERANGE + migration_execute(m_ioctx, m_image_name); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, SnapTrimBeforePrepare) +{ + auto size = m_ictx->size; + + write(size >> 1, 10, 'A'); + snap_create("snap1"); + resize(size >> 1); + + migration_prepare(m_ioctx, m_image_name); + + resize(size); + snap_create("snap3"); + write(size >> 1, 10, 'B'); + snap_create("snap4"); + resize(size >> 1); + + migration_execute(m_ioctx, m_image_name); + migration_commit(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, AbortInUseImage) { + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + librbd::NoOpProgressContext no_op; + EXPECT_EQ(-EBUSY, librbd::api::Migration<>::abort(m_ioctx, m_ictx->name, + no_op)); +} + +TEST_F(TestMigration, AbortWithoutSnapshots) { + test_no_snaps(); + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + test_no_snaps(); + migration_abort(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, AbortWithSnapshots) { + test_snaps(); + migration_prepare(m_ioctx, m_image_name); + migration_status(RBD_IMAGE_MIGRATION_STATE_PREPARED); + + test_no_snaps(); + flush(); + ASSERT_EQ(0, TestFixture::snap_create(*m_ictx, "dst-only-snap")); + + test_no_snaps(); + + migration_abort(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, CloneV1Parent) +{ + const uint32_t CLONE_FORMAT = 1; + test_migrate_parent( + CLONE_FORMAT, [this](librbd::ImageCtx *) { + migrate(m_ioctx, m_image_name); + }); +} + +TEST_F(TestMigration, CloneV2Parent) +{ + const uint32_t CLONE_FORMAT = 2; + test_migrate_parent( + CLONE_FORMAT, [this](librbd::ImageCtx *) { + migrate(m_ioctx, m_image_name); + }); +} + +TEST_F(TestMigration, CloneV1ParentAbort) +{ + const uint32_t CLONE_FORMAT = 1; + test_migrate_parent( + CLONE_FORMAT, [this](librbd::ImageCtx *) { + migration_prepare(m_ioctx, m_image_name); + migration_abort(m_ioctx, m_image_name); + }); +} + +TEST_F(TestMigration, CloneV2ParentAbort) +{ + const uint32_t CLONE_FORMAT = 2; + test_migrate_parent( + CLONE_FORMAT, [this](librbd::ImageCtx *) { + migration_prepare(m_ioctx, m_image_name); + migration_abort(m_ioctx, m_image_name); + }); +} + +TEST_F(TestMigration, CloneV1ParentAbortFixIncompleteChildReattach) +{ + const uint32_t CLONE_FORMAT = 1; + test_migrate_parent( + CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) { + auto src_image_id = m_ictx->id; + migration_prepare(m_ioctx, m_image_name); + // Attach the child to both source and destination + // to emulate a crash when re-attaching the child + librbd::ImageCtx *src_ictx; + open_image(m_ioctx, "", src_image_id, false, + librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx); + C_SaferCond cond; + auto req = librbd::image::AttachChildRequest<>::create( + child_ictx, src_ictx, src_ictx->snaps[0], nullptr, 0, + CLONE_FORMAT, &cond); + req->send(); + ASSERT_EQ(0, cond.wait()); + close_image(src_ictx); + migration_abort(m_ioctx, m_image_name); + }); +} + +TEST_F(TestMigration, CloneV1ParentAbortFixParentReattach) +{ + const uint32_t CLONE_FORMAT = 1; + test_migrate_parent( + CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) { + auto src_image_id = m_ictx->id; + migration_prepare(m_ioctx, m_image_name); + // Re-attach the child back to the source to emulate a crash + // after the parent reattach but before the child reattach + librbd::ImageCtx *src_ictx; + open_image(m_ioctx, "", src_image_id, false, + librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx); + C_SaferCond cond; + auto req = librbd::image::AttachChildRequest<>::create( + child_ictx, src_ictx, src_ictx->snaps[0], m_ictx, + m_ictx->snaps[0], CLONE_FORMAT, &cond); + req->send(); + ASSERT_EQ(0, cond.wait()); + close_image(src_ictx); + migration_abort(m_ioctx, m_image_name); + }); +} + +TEST_F(TestMigration, CloneV1ParentAbortRelinkNotNeeded) +{ + const uint32_t CLONE_FORMAT = 1; + test_migrate_parent( + CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) { + auto src_image_id = m_ictx->id; + auto parent_spec = child_ictx->parent_md.spec; + parent_spec.image_id = m_ictx->id; + parent_spec.snap_id = m_ictx->snaps[0]; + auto parent_overlap = child_ictx->parent_md.overlap; + migration_prepare(m_ioctx, m_image_name); + // Relink the child back to emulate a crash + // before relinking the child + C_SaferCond cond; + auto req = librbd::image::AttachParentRequest<>::create( + *child_ictx, parent_spec, parent_overlap, true, &cond); + req->send(); + ASSERT_EQ(0, cond.wait()); + librbd::ImageCtx *src_ictx; + open_image(m_ioctx, "", src_image_id, false, + librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx); + C_SaferCond cond1; + auto req1 = librbd::image::AttachChildRequest<>::create( + child_ictx, src_ictx, src_ictx->snaps[0], m_ictx, + m_ictx->snaps[0], CLONE_FORMAT, &cond1); + req1->send(); + ASSERT_EQ(0, cond1.wait()); + close_image(src_ictx); + migration_abort(m_ioctx, m_image_name); + }); +} + +TEST_F(TestMigration, CloneV2ParentAbortFixIncompleteChildReattach) +{ + const uint32_t CLONE_FORMAT = 2; + test_migrate_parent( + CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) { + auto src_image_id = m_ictx->id; + migration_prepare(m_ioctx, m_image_name); + // Attach the child to both source and destination + // to emulate a crash when re-attaching the child + librbd::ImageCtx *src_ictx; + open_image(m_ioctx, "", src_image_id, false, + librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx); + C_SaferCond cond; + auto req = librbd::image::AttachChildRequest<>::create( + child_ictx, src_ictx, src_ictx->snaps[0], nullptr, 0, + CLONE_FORMAT, &cond); + req->send(); + ASSERT_EQ(0, cond.wait()); + close_image(src_ictx); + migration_abort(m_ioctx, m_image_name); + }); +} + +TEST_F(TestMigration, CloneV2ParentAbortFixParentReattach) +{ + const uint32_t CLONE_FORMAT = 2; + test_migrate_parent( + CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) { + auto src_image_id = m_ictx->id; + migration_prepare(m_ioctx, m_image_name); + // Re-attach the child back to the source to emulate a crash + // after the parent reattach but before the child reattach + librbd::ImageCtx *src_ictx; + open_image(m_ioctx, "", src_image_id, false, + librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx); + C_SaferCond cond; + auto req = librbd::image::AttachChildRequest<>::create( + child_ictx, src_ictx, src_ictx->snaps[0], m_ictx, + m_ictx->snaps[0], CLONE_FORMAT, &cond); + req->send(); + ASSERT_EQ(0, cond.wait()); + close_image(src_ictx); + migration_abort(m_ioctx, m_image_name); + }); +} + +TEST_F(TestMigration, CloneV2ParentAbortRelinkNotNeeded) +{ + const uint32_t CLONE_FORMAT = 2; + test_migrate_parent( + CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) { + auto src_image_id = m_ictx->id; + auto parent_spec = child_ictx->parent_md.spec; + parent_spec.image_id = m_ictx->id; + parent_spec.snap_id = m_ictx->snaps[0]; + auto parent_overlap = child_ictx->parent_md.overlap; + migration_prepare(m_ioctx, m_image_name); + // Relink the child back to emulate a crash + // before relinking the child + C_SaferCond cond; + auto req = librbd::image::AttachParentRequest<>::create( + *child_ictx, parent_spec, parent_overlap, true, &cond); + req->send(); + ASSERT_EQ(0, cond.wait()); + librbd::ImageCtx *src_ictx; + open_image(m_ioctx, "", src_image_id, false, + librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx); + C_SaferCond cond1; + auto req1 = librbd::image::AttachChildRequest<>::create( + child_ictx, src_ictx, src_ictx->snaps[0], m_ictx, + m_ictx->snaps[0], CLONE_FORMAT, &cond1); + req1->send(); + ASSERT_EQ(0, cond1.wait()); + close_image(src_ictx); + migration_abort(m_ioctx, m_image_name); + }); +} + +TEST_F(TestMigration, StressNoMigrate) +{ + test_stress(); + + compare(); +} + +TEST_F(TestMigration, Stress) +{ + test_stress(); + + migrate(m_ioctx, m_image_name); +} + +TEST_F(TestMigration, Stress2) +{ + test_stress2(false); +} + +TEST_F(TestMigration, StressLive) +{ + test_stress2(true); +} diff --git a/src/test/librbd/test_MirroringWatcher.cc b/src/test/librbd/test_MirroringWatcher.cc new file mode 100644 index 00000000..6038b554 --- /dev/null +++ b/src/test/librbd/test_MirroringWatcher.cc @@ -0,0 +1,101 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "include/rbd_types.h" +#include "librbd/MirroringWatcher.h" +#include "common/Cond.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include <list> + +void register_test_mirroring_watcher() { +} + +namespace librbd { + +namespace { + +struct MockMirroringWatcher : public MirroringWatcher<> { + std::string oid; + + MockMirroringWatcher(ImageCtx &image_ctx) + : MirroringWatcher<>(image_ctx.md_ctx, image_ctx.op_work_queue) { + } + + MOCK_METHOD1(handle_mode_updated, void(cls::rbd::MirrorMode)); + MOCK_METHOD3(handle_image_updated, void(cls::rbd::MirrorImageState, + const std::string &, + const std::string &)); +}; + +} // anonymous namespace + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Invoke; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMirroringWatcher : public TestFixture { +public: + void SetUp() override { + TestFixture::SetUp(); + + bufferlist bl; + ASSERT_EQ(0, m_ioctx.write_full(RBD_MIRRORING, bl)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + m_image_watcher = new MockMirroringWatcher(*ictx); + C_SaferCond ctx; + m_image_watcher->register_watch(&ctx); + if (ctx.wait() != 0) { + delete m_image_watcher; + m_image_watcher = nullptr; + FAIL(); + } + } + + void TearDown() override { + if (m_image_watcher != nullptr) { + C_SaferCond ctx; + m_image_watcher->unregister_watch(&ctx); + ASSERT_EQ(0, ctx.wait()); + delete m_image_watcher; + } + + TestFixture::TearDown(); + } + + MockMirroringWatcher *m_image_watcher = nullptr; +}; + +TEST_F(TestMirroringWatcher, ModeUpdated) { + EXPECT_CALL(*m_image_watcher, + handle_mode_updated(cls::rbd::MIRROR_MODE_DISABLED)) + .Times(AtLeast(1)); + + C_SaferCond ctx; + MockMirroringWatcher::notify_mode_updated( + m_ioctx, cls::rbd::MIRROR_MODE_DISABLED, &ctx); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMirroringWatcher, ImageStatusUpdated) { + EXPECT_CALL(*m_image_watcher, + handle_image_updated(cls::rbd::MIRROR_IMAGE_STATE_ENABLED, + StrEq("image id"), + StrEq("global image id"))) + .Times(AtLeast(1)); + + C_SaferCond ctx; + MockMirroringWatcher::notify_image_updated( + m_ioctx, cls::rbd::MIRROR_IMAGE_STATE_ENABLED, "image id", + "global image id", &ctx); + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace librbd diff --git a/src/test/librbd/test_ObjectMap.cc b/src/test/librbd/test_ObjectMap.cc new file mode 100644 index 00000000..d939ffb9 --- /dev/null +++ b/src/test/librbd/test_ObjectMap.cc @@ -0,0 +1,236 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/ImageWatcher.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "common/Cond.h" +#include "common/Throttle.h" +#include "cls/rbd/cls_rbd_client.h" +#include "cls/rbd/cls_rbd_types.h" +#include <list> +#include <boost/accumulators/accumulators.hpp> +#include <boost/accumulators/statistics/stats.hpp> +#include <boost/accumulators/statistics/rolling_sum.hpp> + +void register_test_object_map() { +} + +class TestObjectMap : public TestFixture { +public: + + int when_open_object_map(librbd::ImageCtx *ictx) { + C_SaferCond ctx; + librbd::ObjectMap<> object_map(*ictx, ictx->snap_id); + object_map.open(&ctx); + return ctx.wait(); + } +}; + +TEST_F(TestObjectMap, RefreshInvalidatesWhenCorrupt) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + bool flags_set; + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, RBD_FLAG_OBJECT_MAP_INVALID, + &flags_set)); + ASSERT_FALSE(flags_set); + + C_SaferCond lock_ctx; + { + RWLock::WLocker owner_locker(ictx->owner_lock); + ictx->exclusive_lock->try_acquire_lock(&lock_ctx); + } + ASSERT_EQ(0, lock_ctx.wait()); + + std::string oid = librbd::ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP); + bufferlist bl; + bl.append("corrupt"); + ASSERT_EQ(0, ictx->md_ctx.write_full(oid, bl)); + + ASSERT_EQ(0, when_open_object_map(ictx)); + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, RBD_FLAG_OBJECT_MAP_INVALID, + &flags_set)); + ASSERT_TRUE(flags_set); +} + +TEST_F(TestObjectMap, RefreshInvalidatesWhenTooSmall) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + bool flags_set; + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, RBD_FLAG_OBJECT_MAP_INVALID, + &flags_set)); + ASSERT_FALSE(flags_set); + + C_SaferCond lock_ctx; + { + RWLock::WLocker owner_locker(ictx->owner_lock); + ictx->exclusive_lock->try_acquire_lock(&lock_ctx); + } + ASSERT_EQ(0, lock_ctx.wait()); + + librados::ObjectWriteOperation op; + librbd::cls_client::object_map_resize(&op, 0, OBJECT_NONEXISTENT); + + std::string oid = librbd::ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP); + ASSERT_EQ(0, ictx->md_ctx.operate(oid, &op)); + + ASSERT_EQ(0, when_open_object_map(ictx)); + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, RBD_FLAG_OBJECT_MAP_INVALID, + &flags_set)); + ASSERT_TRUE(flags_set); +} + +TEST_F(TestObjectMap, InvalidateFlagOnDisk) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + bool flags_set; + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, RBD_FLAG_OBJECT_MAP_INVALID, + &flags_set)); + ASSERT_FALSE(flags_set); + + C_SaferCond lock_ctx; + { + RWLock::WLocker owner_locker(ictx->owner_lock); + ictx->exclusive_lock->try_acquire_lock(&lock_ctx); + } + ASSERT_EQ(0, lock_ctx.wait()); + + std::string oid = librbd::ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP); + bufferlist bl; + bl.append("corrupt"); + ASSERT_EQ(0, ictx->md_ctx.write_full(oid, bl)); + + ASSERT_EQ(0, when_open_object_map(ictx)); + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, RBD_FLAG_OBJECT_MAP_INVALID, + &flags_set)); + ASSERT_TRUE(flags_set); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, RBD_FLAG_OBJECT_MAP_INVALID, + &flags_set)); + ASSERT_TRUE(flags_set); +} + +TEST_F(TestObjectMap, AcquireLockInvalidatesWhenTooSmall) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + bool flags_set; + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, RBD_FLAG_OBJECT_MAP_INVALID, + &flags_set)); + ASSERT_FALSE(flags_set); + + librados::ObjectWriteOperation op; + librbd::cls_client::object_map_resize(&op, 0, OBJECT_NONEXISTENT); + + std::string oid = librbd::ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP); + ASSERT_EQ(0, ictx->md_ctx.operate(oid, &op)); + + C_SaferCond lock_ctx; + { + RWLock::WLocker owner_locker(ictx->owner_lock); + ictx->exclusive_lock->try_acquire_lock(&lock_ctx); + } + ASSERT_EQ(0, lock_ctx.wait()); + + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, RBD_FLAG_OBJECT_MAP_INVALID, + &flags_set)); + ASSERT_TRUE(flags_set); + + // Test the flag is stored on disk + ASSERT_EQ(0, ictx->state->refresh()); + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, RBD_FLAG_OBJECT_MAP_INVALID, + &flags_set)); + ASSERT_TRUE(flags_set); +} + +TEST_F(TestObjectMap, DISABLED_StressTest) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + uint64_t object_count = cls::rbd::MAX_OBJECT_MAP_OBJECT_COUNT; + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, resize(ictx, ictx->layout.object_size * object_count)); + + bool flags_set; + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, RBD_FLAG_OBJECT_MAP_INVALID, + &flags_set)); + ASSERT_FALSE(flags_set); + + srand(time(NULL) % (unsigned long) -1); + + coarse_mono_time start = coarse_mono_clock::now(); + chrono::duration<double> last = chrono::duration<double>::zero(); + + const int WINDOW_SIZE = 5; + typedef boost::accumulators::accumulator_set< + double, boost::accumulators::stats< + boost::accumulators::tag::rolling_sum> > RollingSum; + + RollingSum time_acc( + boost::accumulators::tag::rolling_window::window_size = WINDOW_SIZE); + RollingSum ios_acc( + boost::accumulators::tag::rolling_window::window_size = WINDOW_SIZE); + + uint32_t io_threads = 16; + uint64_t cur_ios = 0; + SimpleThrottle throttle(io_threads, false); + for (uint64_t ios = 0; ios < 100000;) { + if (throttle.pending_error()) { + break; + } + + throttle.start_op(); + uint64_t object_no = (rand() % object_count); + auto ctx = new FunctionContext([&throttle, object_no](int r) { + ASSERT_EQ(0, r) << "object_no=" << object_no; + throttle.end_op(r); + }); + + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->object_map_lock); + ASSERT_TRUE(ictx->object_map != nullptr); + + if (!ictx->object_map->aio_update< + Context, &Context::complete>(CEPH_NOSNAP, object_no, + OBJECT_EXISTS, {}, {}, true, + ctx)) { + ctx->complete(0); + } else { + ++cur_ios; + ++ios; + } + + coarse_mono_time now = coarse_mono_clock::now(); + chrono::duration<double> elapsed = now - start; + if (last == chrono::duration<double>::zero()) { + last = elapsed; + } else if ((int)elapsed.count() != (int)last.count()) { + time_acc((elapsed - last).count()); + ios_acc(static_cast<double>(cur_ios)); + cur_ios = 0; + + double time_sum = boost::accumulators::rolling_sum(time_acc); + std::cerr << std::setw(5) << (int)elapsed.count() << "\t" + << std::setw(8) << (int)ios << "\t" + << std::fixed << std::setw(8) << std::setprecision(2) + << boost::accumulators::rolling_sum(ios_acc) / time_sum + << std::endl; + last = elapsed; + } + } + + ASSERT_EQ(0, throttle.wait_for_ret()); +} diff --git a/src/test/librbd/test_Operations.cc b/src/test/librbd/test_Operations.cc new file mode 100644 index 00000000..43921df5 --- /dev/null +++ b/src/test/librbd/test_Operations.cc @@ -0,0 +1,26 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "librbd/ImageCtx.h" +#include "librbd/Operations.h" + +void register_test_operations() { +} + +class TestOperations : public TestFixture { +public: + +}; + +TEST_F(TestOperations, DisableJournalingCorrupt) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, m_ioctx.remove("journal." + ictx->id)); + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); +} + diff --git a/src/test/librbd/test_Trash.cc b/src/test/librbd/test_Trash.cc new file mode 100644 index 00000000..b4770827 --- /dev/null +++ b/src/test/librbd/test_Trash.cc @@ -0,0 +1,108 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "cls/rbd/cls_rbd_client.h" +#include "cls/rbd/cls_rbd_types.h" +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "librbd/api/Trash.h" +#include <set> +#include <vector> + +void register_test_trash() { +} + +namespace librbd { + +static bool operator==(const trash_image_info_t& lhs, + const trash_image_info_t& rhs) { + return (lhs.id == rhs.id && + lhs.name == rhs.name && + lhs.source == rhs.source); +} + +static bool operator==(const image_spec_t& lhs, + const image_spec_t& rhs) { + return (lhs.id == rhs.id && lhs.name == rhs.name); +} + +class TestTrash : public TestFixture { +public: + + TestTrash() {} +}; + +TEST_F(TestTrash, UserRemovingSource) { + REQUIRE_FORMAT_V2(); + + auto compare_lambda = [](const trash_image_info_t& lhs, + const trash_image_info_t& rhs) { + if (lhs.id != rhs.id) { + return lhs.id < rhs.id; + } else if (lhs.name != rhs.name) { + return lhs.name < rhs.name; + } + return lhs.source < rhs.source; + }; + typedef std::set<trash_image_info_t, decltype(compare_lambda)> TrashEntries; + + librbd::RBD rbd; + librbd::Image image; + auto image_name1 = m_image_name; + std::string image_id1; + ASSERT_EQ(0, rbd.open(m_ioctx, image, image_name1.c_str())); + ASSERT_EQ(0, image.get_id(&image_id1)); + ASSERT_EQ(0, image.close()); + + auto image_name2 = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(m_rbd, m_ioctx, image_name2, m_image_size)); + + std::string image_id2; + ASSERT_EQ(0, rbd.open(m_ioctx, image, image_name2.c_str())); + ASSERT_EQ(0, image.get_id(&image_id2)); + ASSERT_EQ(0, image.close()); + + ASSERT_EQ(0, api::Trash<>::move(m_ioctx, RBD_TRASH_IMAGE_SOURCE_USER, + image_name1, image_id1, 0)); + ASSERT_EQ(0, api::Trash<>::move(m_ioctx, RBD_TRASH_IMAGE_SOURCE_REMOVING, + image_name2, image_id2, 0)); + + TrashEntries trash_entries{compare_lambda}; + TrashEntries expected_trash_entries{compare_lambda}; + + std::vector<trash_image_info_t> entries; + ASSERT_EQ(0, api::Trash<>::list(m_ioctx, entries, true)); + trash_entries.insert(entries.begin(), entries.end()); + + expected_trash_entries = { + {.id = image_id1, + .name = image_name1, + .source = RBD_TRASH_IMAGE_SOURCE_USER}, + }; + ASSERT_EQ(expected_trash_entries, trash_entries); + + std::vector<image_spec_t> expected_images = { + {.id = image_id2, .name = image_name2} + }; + std::vector<image_spec_t> images; + ASSERT_EQ(0, rbd.list2(m_ioctx, &images)); + ASSERT_EQ(expected_images, images); +} + +TEST_F(TestTrash, RestoreMirroringSource) { + REQUIRE_FORMAT_V2(); + + librbd::RBD rbd; + librbd::Image image; + std::string image_id; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str())); + ASSERT_EQ(0, image.get_id(&image_id)); + ASSERT_EQ(0, image.close()); + + ASSERT_EQ(0, api::Trash<>::move(m_ioctx, RBD_TRASH_IMAGE_SOURCE_MIRRORING, + m_image_name, 0)); + ASSERT_EQ(0, rbd.trash_restore(m_ioctx, image_id.c_str(), + m_image_name.c_str())); +} + +} // namespace librbd diff --git a/src/test/librbd/test_fixture.cc b/src/test/librbd/test_fixture.cc new file mode 100644 index 00000000..e9ea63d4 --- /dev/null +++ b/src/test/librbd/test_fixture.cc @@ -0,0 +1,145 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "include/stringify.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageState.h" +#include "librbd/ImageWatcher.h" +#include "librbd/Operations.h" +#include "librbd/io/ImageRequestWQ.h" +#include "cls/lock/cls_lock_client.h" +#include "cls/lock/cls_lock_types.h" +#include "cls/rbd/cls_rbd_types.h" +#include "librbd/internal.h" +#include "test/librados/test.h" +#include "test/librados/test_cxx.h" +#include <iostream> +#include <sstream> +#include <stdlib.h> + +std::string TestFixture::_pool_name; +librados::Rados TestFixture::_rados; +rados_t TestFixture::_cluster; +uint64_t TestFixture::_image_number = 0; +std::string TestFixture::_data_pool; + +TestFixture::TestFixture() : m_image_size(0) { +} + +void TestFixture::SetUpTestCase() { + ASSERT_EQ("", connect_cluster(&_cluster)); + _pool_name = get_temp_pool_name("test-librbd-"); + ASSERT_EQ("", create_one_pool_pp(_pool_name, _rados)); + + bool created = false; + ASSERT_EQ(0, create_image_data_pool(_rados, _data_pool, &created)); + if (!_data_pool.empty()) { + printf("using image data pool: %s\n", _data_pool.c_str()); + if (!created) { + _data_pool.clear(); + } + } +} + +void TestFixture::TearDownTestCase() { + rados_shutdown(_cluster); + if (!_data_pool.empty()) { + ASSERT_EQ(0, _rados.pool_delete(_data_pool.c_str())); + } + + ASSERT_EQ(0, destroy_one_pool_pp(_pool_name, _rados)); +} + +std::string TestFixture::get_temp_image_name() { + ++_image_number; + return "image" + stringify(_image_number); +} + +void TestFixture::SetUp() { + ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), m_ioctx)); + m_cct = reinterpret_cast<CephContext*>(m_ioctx.cct()); + + m_image_name = get_temp_image_name(); + m_image_size = 2 << 20; + ASSERT_EQ(0, create_image_pp(m_rbd, m_ioctx, m_image_name, m_image_size)); +} + +void TestFixture::TearDown() { + unlock_image(); + for (std::set<librbd::ImageCtx *>::iterator iter = m_ictxs.begin(); + iter != m_ictxs.end(); ++iter) { + (*iter)->state->close(); + } + + m_ioctx.close(); +} + +int TestFixture::open_image(const std::string &image_name, + librbd::ImageCtx **ictx) { + *ictx = new librbd::ImageCtx(image_name.c_str(), "", nullptr, m_ioctx, false); + m_ictxs.insert(*ictx); + + return (*ictx)->state->open(0); +} + +int TestFixture::snap_create(librbd::ImageCtx &ictx, + const std::string &snap_name) { + return ictx.operations->snap_create(cls::rbd::UserSnapshotNamespace(), + snap_name.c_str()); +} + +int TestFixture::snap_protect(librbd::ImageCtx &ictx, + const std::string &snap_name) { + return ictx.operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + snap_name.c_str()); +} + +int TestFixture::flatten(librbd::ImageCtx &ictx, + librbd::ProgressContext &prog_ctx) { + return ictx.operations->flatten(prog_ctx); +} + +int TestFixture::resize(librbd::ImageCtx *ictx, uint64_t size){ + librbd::NoOpProgressContext prog_ctx; + return ictx->operations->resize(size, true, prog_ctx); +} + +void TestFixture::close_image(librbd::ImageCtx *ictx) { + m_ictxs.erase(ictx); + + ictx->state->close(); +} + +int TestFixture::lock_image(librbd::ImageCtx &ictx, ClsLockType lock_type, + const std::string &cookie) { + int r = rados::cls::lock::lock(&ictx.md_ctx, ictx.header_oid, RBD_LOCK_NAME, + lock_type, cookie, "internal", "", utime_t(), + 0); + if (r == 0) { + m_lock_object = ictx.header_oid; + m_lock_cookie = cookie; + } + return r; +} + +int TestFixture::unlock_image() { + int r = 0; + if (!m_lock_cookie.empty()) { + r = rados::cls::lock::unlock(&m_ioctx, m_lock_object, RBD_LOCK_NAME, + m_lock_cookie); + m_lock_cookie = ""; + } + return r; +} + +int TestFixture::acquire_exclusive_lock(librbd::ImageCtx &ictx) { + int r = ictx.io_work_queue->write(0, 0, {}, 0); + if (r != 0) { + return r; + } + + RWLock::RLocker owner_locker(ictx.owner_lock); + ceph_assert(ictx.exclusive_lock != nullptr); + return ictx.exclusive_lock->is_lock_owner() ? 0 : -EINVAL; +} diff --git a/src/test/librbd/test_fixture.h b/src/test/librbd/test_fixture.h new file mode 100644 index 00000000..508e4405 --- /dev/null +++ b/src/test/librbd/test_fixture.h @@ -0,0 +1,58 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#include "include/int_types.h" +#include "include/rados/librados.h" +#include "include/rbd/librbd.hpp" +#include "librbd/ImageCtx.h" +#include "gtest/gtest.h" +#include <set> +#include <string> + +using namespace ceph; + +class TestFixture : public ::testing::Test { +public: + + TestFixture(); + + static void SetUpTestCase(); + static void TearDownTestCase(); + + static std::string get_temp_image_name(); + + void SetUp() override; + void TearDown() override; + + int open_image(const std::string &image_name, librbd::ImageCtx **ictx); + void close_image(librbd::ImageCtx *ictx); + + int snap_create(librbd::ImageCtx &ictx, const std::string &snap_name); + int snap_protect(librbd::ImageCtx &ictx, const std::string &snap_name); + + int flatten(librbd::ImageCtx &ictx, librbd::ProgressContext &prog_ctx); + int resize(librbd::ImageCtx *ictx, uint64_t size); + + int lock_image(librbd::ImageCtx &ictx, ClsLockType lock_type, + const std::string &cookie); + int unlock_image(); + + int acquire_exclusive_lock(librbd::ImageCtx &ictx); + + static std::string _pool_name; + static librados::Rados _rados; + static rados_t _cluster; + static uint64_t _image_number; + static std::string _data_pool; + + CephContext* m_cct = nullptr; + librados::IoCtx m_ioctx; + librbd::RBD m_rbd; + + std::string m_image_name; + uint64_t m_image_size; + + std::set<librbd::ImageCtx *> m_ictxs; + + std::string m_lock_object; + std::string m_lock_cookie; +}; diff --git a/src/test/librbd/test_internal.cc b/src/test/librbd/test_internal.cc new file mode 100644 index 00000000..0caf9fff --- /dev/null +++ b/src/test/librbd/test_internal.cc @@ -0,0 +1,1754 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "cls/journal/cls_journal_client.h" +#include "cls/rbd/cls_rbd_client.h" +#include "cls/rbd/cls_rbd_types.h" +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "include/rbd/librbd.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageState.h" +#include "librbd/ImageWatcher.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/Operations.h" +#include "librbd/api/DiffIterate.h" +#include "librbd/api/Image.h" +#include "librbd/api/Migration.h" +#include "librbd/api/PoolMetadata.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/io/ImageRequest.h" +#include "librbd/io/ImageRequestWQ.h" +#include "osdc/Striper.h" +#include <boost/scope_exit.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/assign/list_of.hpp> +#include <utility> +#include <vector> + +void register_test_internal() { +} + +class TestInternal : public TestFixture { +public: + + TestInternal() {} + + typedef std::vector<std::pair<std::string, bool> > Snaps; + + void TearDown() override { + unlock_image(); + for (Snaps::iterator iter = m_snaps.begin(); iter != m_snaps.end(); ++iter) { + librbd::ImageCtx *ictx; + EXPECT_EQ(0, open_image(m_image_name, &ictx)); + if (iter->second) { + EXPECT_EQ(0, + ictx->operations->snap_unprotect(cls::rbd::UserSnapshotNamespace(), + iter->first.c_str())); + } + EXPECT_EQ(0, + ictx->operations->snap_remove(cls::rbd::UserSnapshotNamespace(), + iter->first.c_str())); + } + + TestFixture::TearDown(); + } + + int create_snapshot(const char *snap_name, bool snap_protect) { + librbd::ImageCtx *ictx; + int r = open_image(m_image_name, &ictx); + if (r < 0) { + return r; + } + + r = snap_create(*ictx, snap_name); + if (r < 0) { + return r; + } + + m_snaps.push_back(std::make_pair(snap_name, snap_protect)); + if (snap_protect) { + r = ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), snap_name); + if (r < 0) { + return r; + } + } + close_image(ictx); + return 0; + } + + Snaps m_snaps; +}; + +class DummyContext : public Context { +public: + void finish(int r) override { + } +}; + +void generate_random_iomap(librbd::Image &image, int num_objects, int object_size, + int max_count, map<uint64_t, uint64_t> &iomap) +{ + uint64_t stripe_unit, stripe_count; + + stripe_unit = image.get_stripe_unit(); + stripe_count = image.get_stripe_count(); + + while (max_count-- > 0) { + // generate random image offset based on base random object + // number and object offset and then map that back to an + // object number based on stripe unit and count. + uint64_t ono = rand() % num_objects; + uint64_t offset = rand() % (object_size - TEST_IO_SIZE); + uint64_t imageoff = (ono * object_size) + offset; + + file_layout_t layout; + layout.object_size = object_size; + layout.stripe_unit = stripe_unit; + layout.stripe_count = stripe_count; + + vector<ObjectExtent> ex; + Striper::file_to_extents(g_ceph_context, 1, &layout, imageoff, TEST_IO_SIZE, 0, ex); + + // lets not worry if IO spans multiple extents (>1 object). in such + // as case we would perform the write multiple times to the same + // offset, but we record all objects that would be generated with + // this IO. TODO: fix this if such a need is required by your + // test. + vector<ObjectExtent>::iterator it; + map<uint64_t, uint64_t> curr_iomap; + for (it = ex.begin(); it != ex.end(); ++it) { + if (iomap.find((*it).objectno) != iomap.end()) { + break; + } + + curr_iomap.insert(make_pair((*it).objectno, imageoff)); + } + + if (it == ex.end()) { + iomap.insert(curr_iomap.begin(), curr_iomap.end()); + } + } +} + +static bool is_sparsify_supported(librados::IoCtx &ioctx, + const std::string &oid) { + EXPECT_EQ(0, ioctx.create(oid, true)); + int r = librbd::cls_client::sparsify(&ioctx, oid, 16, true); + EXPECT_TRUE(r == 0 || r == -EOPNOTSUPP); + ioctx.remove(oid); + + return (r == 0); +} + +static bool is_sparse_read_supported(librados::IoCtx &ioctx, + const std::string &oid) { + EXPECT_EQ(0, ioctx.create(oid, true)); + bufferlist inbl; + inbl.append(std::string(1, 'X')); + EXPECT_EQ(0, ioctx.write(oid, inbl, inbl.length(), 1)); + EXPECT_EQ(0, ioctx.write(oid, inbl, inbl.length(), 3)); + + std::map<uint64_t, uint64_t> m; + bufferlist outbl; + int r = ioctx.sparse_read(oid, m, outbl, 4, 0); + ioctx.remove(oid); + + int expected_r = 2; + std::map<uint64_t, uint64_t> expected_m = {{1, 1}, {3, 1}}; + bufferlist expected_outbl; + expected_outbl.append(std::string(2, 'X')); + + return (r == expected_r && m == expected_m && + outbl.contents_equal(expected_outbl)); +} + +TEST_F(TestInternal, OpenByID) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + std::string id = ictx->id; + close_image(ictx); + + ictx = new librbd::ImageCtx("", id, nullptr, m_ioctx, true); + ASSERT_EQ(0, ictx->state->open(0)); + ASSERT_EQ(ictx->name, m_image_name); + close_image(ictx); +} + +TEST_F(TestInternal, OpenSnapDNE) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ictx = new librbd::ImageCtx(m_image_name, "", "unknown_snap", m_ioctx, true); + ASSERT_EQ(-ENOENT, ictx->state->open(librbd::OPEN_FLAG_SKIP_OPEN_PARENT)); +} + +TEST_F(TestInternal, IsExclusiveLockOwner) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + bool is_owner; + ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner)); + ASSERT_FALSE(is_owner); + + C_SaferCond ctx; + { + RWLock::WLocker l(ictx->owner_lock); + ictx->exclusive_lock->try_acquire_lock(&ctx); + } + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner)); + ASSERT_TRUE(is_owner); +} + +TEST_F(TestInternal, ResizeLocksImage) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, ictx->operations->resize(m_image_size >> 1, true, no_op)); + + bool is_owner; + ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner)); + ASSERT_TRUE(is_owner); +} + +TEST_F(TestInternal, ResizeFailsToLockImage) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked")); + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(-EROFS, ictx->operations->resize(m_image_size >> 1, true, no_op)); +} + +TEST_F(TestInternal, SnapCreateLocksImage) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + BOOST_SCOPE_EXIT( (ictx) ) { + ASSERT_EQ(0, + ictx->operations->snap_remove(cls::rbd::UserSnapshotNamespace(), + "snap1")); + } BOOST_SCOPE_EXIT_END; + + bool is_owner; + ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner)); + ASSERT_TRUE(is_owner); +} + +TEST_F(TestInternal, SnapCreateFailsToLockImage) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked")); + + ASSERT_EQ(-EROFS, snap_create(*ictx, "snap1")); +} + +TEST_F(TestInternal, SnapRollbackLocksImage) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + ASSERT_EQ(0, create_snapshot("snap1", false)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, ictx->operations->snap_rollback(cls::rbd::UserSnapshotNamespace(), + "snap1", + no_op)); + + bool is_owner; + ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner)); + ASSERT_TRUE(is_owner); +} + +TEST_F(TestInternal, SnapRollbackFailsToLockImage) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + + ASSERT_EQ(0, create_snapshot("snap1", false)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked")); + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(-EROFS, + ictx->operations->snap_rollback(cls::rbd::UserSnapshotNamespace(), + "snap1", + no_op)); +} + +TEST_F(TestInternal, SnapSetReleasesLock) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + ASSERT_EQ(0, create_snapshot("snap1", false)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::api::Image<>::snap_set( + ictx, cls::rbd::UserSnapshotNamespace(), "snap1")); + + bool is_owner; + ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner)); + ASSERT_FALSE(is_owner); +} + +TEST_F(TestInternal, FlattenLocksImage) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snapshot("snap1", true)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + std::string clone_name = get_temp_image_name(); + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx2; + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, ictx2->operations->flatten(no_op)); + + bool is_owner; + ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx2, &is_owner)); + ASSERT_TRUE(is_owner); +} + +TEST_F(TestInternal, FlattenFailsToLockImage) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snapshot("snap1", true)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + std::string clone_name = get_temp_image_name(); + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + TestInternal *parent = this; + librbd::ImageCtx *ictx2 = NULL; + BOOST_SCOPE_EXIT( (&m_ioctx) (clone_name) (parent) (&ictx2) ) { + if (ictx2 != NULL) { + parent->close_image(ictx2); + parent->unlock_image(); + } + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name, no_op)); + } BOOST_SCOPE_EXIT_END; + + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + ASSERT_EQ(0, lock_image(*ictx2, LOCK_EXCLUSIVE, "manually locked")); + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(-EROFS, ictx2->operations->flatten(no_op)); +} + +TEST_F(TestInternal, AioWriteRequestsLock) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked")); + + std::string buffer(256, '1'); + Context *ctx = new DummyContext(); + auto c = librbd::io::AioCompletion::create(ctx); + c->get(); + + bufferlist bl; + bl.append(buffer); + ictx->io_work_queue->aio_write(c, 0, buffer.size(), std::move(bl), 0); + + bool is_owner; + ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner)); + ASSERT_FALSE(is_owner); + ASSERT_FALSE(c->is_complete()); + + unlock_image(); + ASSERT_EQ(0, c->wait_for_complete()); + c->put(); +} + +TEST_F(TestInternal, AioDiscardRequestsLock) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked")); + + Context *ctx = new DummyContext(); + auto c = librbd::io::AioCompletion::create(ctx); + c->get(); + ictx->io_work_queue->aio_discard(c, 0, 256, false); + + bool is_owner; + ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner)); + ASSERT_FALSE(is_owner); + ASSERT_FALSE(c->is_complete()); + + unlock_image(); + ASSERT_EQ(0, c->wait_for_complete()); + c->put(); +} + +TEST_F(TestInternal, CancelAsyncResize) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + C_SaferCond ctx; + { + RWLock::WLocker l(ictx->owner_lock); + ictx->exclusive_lock->try_acquire_lock(&ctx); + } + + ASSERT_EQ(0, ctx.wait()); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + ASSERT_TRUE(ictx->exclusive_lock->is_lock_owner()); + } + + uint64_t size; + ASSERT_EQ(0, librbd::get_size(ictx, &size)); + + uint32_t attempts = 0; + while (attempts++ < 20 && size > 0) { + C_SaferCond ctx; + librbd::NoOpProgressContext prog_ctx; + + size -= std::min<uint64_t>(size, 1 << 18); + { + RWLock::RLocker l(ictx->owner_lock); + ictx->operations->execute_resize(size, true, prog_ctx, &ctx, 0); + } + + // try to interrupt the in-progress resize + ictx->cancel_async_requests(); + + int r = ctx.wait(); + if (r == -ERESTART) { + std::cout << "detected canceled async request" << std::endl; + break; + } + ASSERT_EQ(0, r); + } +} + +TEST_F(TestInternal, MultipleResize) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->exclusive_lock != nullptr) { + C_SaferCond ctx; + { + RWLock::WLocker l(ictx->owner_lock); + ictx->exclusive_lock->try_acquire_lock(&ctx); + } + + RWLock::RLocker owner_locker(ictx->owner_lock); + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE(ictx->exclusive_lock->is_lock_owner()); + } + + uint64_t size; + ASSERT_EQ(0, librbd::get_size(ictx, &size)); + uint64_t original_size = size; + + std::vector<C_SaferCond*> contexts; + + uint32_t attempts = 0; + librbd::NoOpProgressContext prog_ctx; + while (size > 0) { + uint64_t new_size = original_size; + if (attempts++ % 2 == 0) { + size -= std::min<uint64_t>(size, 1 << 18); + new_size = size; + } + + RWLock::RLocker l(ictx->owner_lock); + contexts.push_back(new C_SaferCond()); + ictx->operations->execute_resize(new_size, true, prog_ctx, contexts.back(), 0); + } + + for (uint32_t i = 0; i < contexts.size(); ++i) { + ASSERT_EQ(0, contexts[i]->wait()); + delete contexts[i]; + } + + ASSERT_EQ(0, librbd::get_size(ictx, &size)); + ASSERT_EQ(0U, size); +} + +TEST_F(TestInternal, Metadata) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + map<string, bool> test_confs = boost::assign::map_list_of( + "aaaaaaa", false)( + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", false)( + "cccccccccccccc", false); + map<string, bool>::iterator it = test_confs.begin(); + int r; + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + r = ictx->operations->metadata_set(it->first, "value1"); + ASSERT_EQ(0, r); + ++it; + r = ictx->operations->metadata_set(it->first, "value2"); + ASSERT_EQ(0, r); + ++it; + r = ictx->operations->metadata_set(it->first, "value3"); + ASSERT_EQ(0, r); + r = ictx->operations->metadata_set("abcd", "value4"); + ASSERT_EQ(0, r); + r = ictx->operations->metadata_set("xyz", "value5"); + ASSERT_EQ(0, r); + map<string, bufferlist> pairs; + r = librbd::metadata_list(ictx, "", 0, &pairs); + ASSERT_EQ(0, r); + ASSERT_EQ(5u, pairs.size()); + r = ictx->operations->metadata_remove("abcd"); + ASSERT_EQ(0, r); + r = ictx->operations->metadata_remove("xyz"); + ASSERT_EQ(0, r); + pairs.clear(); + r = librbd::metadata_list(ictx, "", 0, &pairs); + ASSERT_EQ(0, r); + ASSERT_EQ(3u, pairs.size()); + string val; + r = librbd::metadata_get(ictx, it->first, &val); + ASSERT_EQ(0, r); + ASSERT_STREQ(val.c_str(), "value3"); +} + +TEST_F(TestInternal, MetadataConfApply) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(-ENOENT, ictx->operations->metadata_remove("conf_rbd_cache")); + + bool cache = ictx->cache; + std::string rbd_conf_cache = cache ? "true" : "false"; + std::string new_rbd_conf_cache = !cache ? "true" : "false"; + + ASSERT_EQ(0, ictx->operations->metadata_set("conf_rbd_cache", + new_rbd_conf_cache)); + ASSERT_EQ(!cache, ictx->cache); + + ASSERT_EQ(0, ictx->operations->metadata_remove("conf_rbd_cache")); + ASSERT_EQ(cache, ictx->cache); +} + +TEST_F(TestInternal, SnapshotCopyup) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + bufferlist bl; + bl.append(std::string(256, '1')); + ASSERT_EQ(256, ictx->io_work_queue->write(0, bl.length(), bufferlist{bl}, 0)); + + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, + ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap1")); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + std::string clone_name = get_temp_image_name(); + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx2; + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + + ASSERT_EQ(0, snap_create(*ictx2, "snap1")); + ASSERT_EQ(0, snap_create(*ictx2, "snap2")); + + ASSERT_EQ(256, ictx2->io_work_queue->write(256, bl.length(), bufferlist{bl}, + 0)); + + librados::IoCtx snap_ctx; + snap_ctx.dup(ictx2->data_ctx); + snap_ctx.snap_set_read(CEPH_SNAPDIR); + + librados::snap_set_t snap_set; + ASSERT_EQ(0, snap_ctx.list_snaps(ictx2->get_object_name(0), &snap_set)); + + std::vector< std::pair<uint64_t,uint64_t> > expected_overlap = + boost::assign::list_of( + std::make_pair(0, 256))( + std::make_pair(512, 2096640)); + ASSERT_EQ(2U, snap_set.clones.size()); + ASSERT_NE(CEPH_NOSNAP, snap_set.clones[0].cloneid); + ASSERT_EQ(2U, snap_set.clones[0].snaps.size()); + ASSERT_EQ(expected_overlap, snap_set.clones[0].overlap); + ASSERT_EQ(CEPH_NOSNAP, snap_set.clones[1].cloneid); + + bufferptr read_ptr(256); + bufferlist read_bl; + read_bl.push_back(read_ptr); + + std::list<std::string> snaps = {"snap1", "snap2", ""}; + librbd::io::ReadResult read_result{&read_bl}; + for (std::list<std::string>::iterator it = snaps.begin(); + it != snaps.end(); ++it) { + const char *snap_name = it->empty() ? NULL : it->c_str(); + ASSERT_EQ(0, librbd::api::Image<>::snap_set( + ictx2, cls::rbd::UserSnapshotNamespace(), snap_name)); + + ASSERT_EQ(256, + ictx2->io_work_queue->read(0, 256, + librbd::io::ReadResult{read_result}, + 0)); + ASSERT_TRUE(bl.contents_equal(read_bl)); + + ASSERT_EQ(256, + ictx2->io_work_queue->read(256, 256, + librbd::io::ReadResult{read_result}, + 0)); + if (snap_name == NULL) { + ASSERT_TRUE(bl.contents_equal(read_bl)); + } else { + ASSERT_TRUE(read_bl.is_zero()); + } + + // verify the object map was properly updated + if ((ictx2->features & RBD_FEATURE_OBJECT_MAP) != 0) { + uint8_t state = OBJECT_EXISTS; + if ((ictx2->features & RBD_FEATURE_FAST_DIFF) != 0 && + it != snaps.begin() && snap_name != NULL) { + state = OBJECT_EXISTS_CLEAN; + } + + librbd::ObjectMap<> object_map(*ictx2, ictx2->snap_id); + C_SaferCond ctx; + object_map.open(&ctx); + ASSERT_EQ(0, ctx.wait()); + + RWLock::WLocker object_map_locker(ictx2->object_map_lock); + ASSERT_EQ(state, object_map[0]); + } + } +} + +TEST_F(TestInternal, SnapshotCopyupZeros) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + // create an empty clone + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, + ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap1")); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + std::string clone_name = get_temp_image_name(); + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx2; + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + + ASSERT_EQ(0, snap_create(*ictx2, "snap1")); + + bufferlist bl; + bl.append(std::string(256, '1')); + ASSERT_EQ(256, ictx2->io_work_queue->write(256, bl.length(), bufferlist{bl}, + 0)); + + librados::IoCtx snap_ctx; + snap_ctx.dup(ictx2->data_ctx); + snap_ctx.snap_set_read(CEPH_SNAPDIR); + + librados::snap_set_t snap_set; + ASSERT_EQ(0, snap_ctx.list_snaps(ictx2->get_object_name(0), &snap_set)); + + // verify that snapshot wasn't affected + ASSERT_EQ(1U, snap_set.clones.size()); + ASSERT_EQ(CEPH_NOSNAP, snap_set.clones[0].cloneid); + + bufferptr read_ptr(256); + bufferlist read_bl; + read_bl.push_back(read_ptr); + + std::list<std::string> snaps = {"snap1", ""}; + librbd::io::ReadResult read_result{&read_bl}; + for (std::list<std::string>::iterator it = snaps.begin(); + it != snaps.end(); ++it) { + const char *snap_name = it->empty() ? NULL : it->c_str(); + ASSERT_EQ(0, librbd::api::Image<>::snap_set( + ictx2, cls::rbd::UserSnapshotNamespace(), snap_name)); + + ASSERT_EQ(256, + ictx2->io_work_queue->read(0, 256, + librbd::io::ReadResult{read_result}, + 0)); + ASSERT_TRUE(read_bl.is_zero()); + + ASSERT_EQ(256, + ictx2->io_work_queue->read(256, 256, + librbd::io::ReadResult{read_result}, + 0)); + if (snap_name == NULL) { + ASSERT_TRUE(bl.contents_equal(read_bl)); + } else { + ASSERT_TRUE(read_bl.is_zero()); + } + + // verify that only HEAD object map was updated + if ((ictx2->features & RBD_FEATURE_OBJECT_MAP) != 0) { + uint8_t state = OBJECT_EXISTS; + if (snap_name != NULL) { + state = OBJECT_NONEXISTENT; + } + + librbd::ObjectMap<> object_map(*ictx2, ictx2->snap_id); + C_SaferCond ctx; + object_map.open(&ctx); + ASSERT_EQ(0, ctx.wait()); + + RWLock::WLocker object_map_locker(ictx2->object_map_lock); + ASSERT_EQ(state, object_map[0]); + } + } +} + +TEST_F(TestInternal, SnapshotCopyupZerosMigration) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + close_image(ictx); + + // migrate an empty image + std::string dst_name = get_temp_image_name(); + librbd::ImageOptions dst_opts; + dst_opts.set(RBD_IMAGE_OPTION_FEATURES, features); + ASSERT_EQ(0, librbd::api::Migration<>::prepare(m_ioctx, m_image_name, + m_ioctx, dst_name, + dst_opts)); + + librbd::ImageCtx *ictx2; + ASSERT_EQ(0, open_image(dst_name, &ictx2)); + + ASSERT_EQ(0, snap_create(*ictx2, "snap1")); + + bufferlist bl; + bl.append(std::string(256, '1')); + ASSERT_EQ(256, ictx2->io_work_queue->write(256, bl.length(), bufferlist{bl}, + 0)); + + librados::IoCtx snap_ctx; + snap_ctx.dup(ictx2->data_ctx); + snap_ctx.snap_set_read(CEPH_SNAPDIR); + + librados::snap_set_t snap_set; + ASSERT_EQ(0, snap_ctx.list_snaps(ictx2->get_object_name(0), &snap_set)); + + // verify that snapshot wasn't affected + ASSERT_EQ(1U, snap_set.clones.size()); + ASSERT_EQ(CEPH_NOSNAP, snap_set.clones[0].cloneid); + + bufferptr read_ptr(256); + bufferlist read_bl; + read_bl.push_back(read_ptr); + + std::list<std::string> snaps = {"snap1", ""}; + librbd::io::ReadResult read_result{&read_bl}; + for (std::list<std::string>::iterator it = snaps.begin(); + it != snaps.end(); ++it) { + const char *snap_name = it->empty() ? NULL : it->c_str(); + ASSERT_EQ(0, librbd::api::Image<>::snap_set( + ictx2, cls::rbd::UserSnapshotNamespace(), snap_name)); + + ASSERT_EQ(256, + ictx2->io_work_queue->read(0, 256, + librbd::io::ReadResult{read_result}, + 0)); + ASSERT_TRUE(read_bl.is_zero()); + + ASSERT_EQ(256, + ictx2->io_work_queue->read(256, 256, + librbd::io::ReadResult{read_result}, + 0)); + if (snap_name == NULL) { + ASSERT_TRUE(bl.contents_equal(read_bl)); + } else { + ASSERT_TRUE(read_bl.is_zero()); + } + + // verify that only HEAD object map was updated + if ((ictx2->features & RBD_FEATURE_OBJECT_MAP) != 0) { + uint8_t state = OBJECT_EXISTS; + if (snap_name != NULL) { + state = OBJECT_NONEXISTENT; + } + + librbd::ObjectMap<> object_map(*ictx2, ictx2->snap_id); + C_SaferCond ctx; + object_map.open(&ctx); + ASSERT_EQ(0, ctx.wait()); + + RWLock::WLocker object_map_locker(ictx2->object_map_lock); + ASSERT_EQ(state, object_map[0]); + } + } +} + +TEST_F(TestInternal, ResizeCopyup) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + m_image_name = get_temp_image_name(); + m_image_size = 1 << 14; + + uint64_t features = 0; + get_features(&features); + int order = 12; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, m_image_name.c_str(), m_image_size, + features, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + bufferlist bl; + bl.append(std::string(4096, '1')); + for (size_t i = 0; i < m_image_size; i += bl.length()) { + ASSERT_EQ((ssize_t)bl.length(), + ictx->io_work_queue->write(i, bl.length(), + bufferlist{bl}, 0)); + } + + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, + ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap1")); + + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx2; + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + ASSERT_EQ(0, snap_create(*ictx2, "snap1")); + + bufferptr read_ptr(bl.length()); + bufferlist read_bl; + read_bl.push_back(read_ptr); + + // verify full / partial object removal properly copyup + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, ictx2->operations->resize(m_image_size - (1 << order) - 32, + true, no_op)); + ASSERT_EQ(0, ictx2->operations->resize(m_image_size - (2 << order) - 32, + true, no_op)); + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx2, + cls::rbd::UserSnapshotNamespace(), + "snap1")); + + { + // hide the parent from the snapshot + RWLock::WLocker snap_locker(ictx2->snap_lock); + ictx2->snap_info.begin()->second.parent = librbd::ParentImageInfo(); + } + + librbd::io::ReadResult read_result{&read_bl}; + for (size_t i = 2 << order; i < m_image_size; i += bl.length()) { + ASSERT_EQ((ssize_t)bl.length(), + ictx2->io_work_queue->read(i, bl.length(), + librbd::io::ReadResult{read_result}, + 0)); + ASSERT_TRUE(bl.contents_equal(read_bl)); + } +} + +TEST_F(TestInternal, DiscardCopyup) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + CephContext* cct = reinterpret_cast<CephContext*>(_rados.cct()); + REQUIRE(!cct->_conf.get_val<bool>("rbd_skip_partial_discard")); + + m_image_name = get_temp_image_name(); + m_image_size = 1 << 14; + + uint64_t features = 0; + get_features(&features); + int order = 12; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, m_image_name.c_str(), m_image_size, + features, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + bufferlist bl; + bl.append(std::string(4096, '1')); + for (size_t i = 0; i < m_image_size; i += bl.length()) { + ASSERT_EQ((ssize_t)bl.length(), + ictx->io_work_queue->write(i, bl.length(), + bufferlist{bl}, 0)); + } + + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, + ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap1")); + + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx2; + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + + ASSERT_EQ(0, snap_create(*ictx2, "snap1")); + + bufferptr read_ptr(bl.length()); + bufferlist read_bl; + read_bl.push_back(read_ptr); + + ASSERT_EQ(static_cast<int>(m_image_size - 64), + ictx2->io_work_queue->discard(32, m_image_size - 64, false)); + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx2, + cls::rbd::UserSnapshotNamespace(), + "snap1")); + + { + // hide the parent from the snapshot + RWLock::WLocker snap_locker(ictx2->snap_lock); + ictx2->snap_info.begin()->second.parent = librbd::ParentImageInfo(); + } + + librbd::io::ReadResult read_result{&read_bl}; + for (size_t i = 0; i < m_image_size; i += bl.length()) { + ASSERT_EQ((ssize_t)bl.length(), + ictx2->io_work_queue->read(i, bl.length(), + librbd::io::ReadResult{read_result}, + 0)); + ASSERT_TRUE(bl.contents_equal(read_bl)); + } +} + +TEST_F(TestInternal, ShrinkFlushesCache) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + std::string buffer(4096, '1'); + + // ensure write-path is initialized + bufferlist write_bl; + write_bl.append(buffer); + ictx->io_work_queue->write(0, buffer.size(), bufferlist{write_bl}, 0); + + C_SaferCond cond_ctx; + auto c = librbd::io::AioCompletion::create(&cond_ctx); + c->get(); + ictx->io_work_queue->aio_write(c, 0, buffer.size(), bufferlist{write_bl}, 0); + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, ictx->operations->resize(m_image_size >> 1, true, no_op)); + + ASSERT_TRUE(c->is_complete()); + ASSERT_EQ(0, c->wait_for_complete()); + ASSERT_EQ(0, cond_ctx.wait()); + c->put(); +} + +TEST_F(TestInternal, ImageOptions) { + rbd_image_options_t opts1 = NULL, opts2 = NULL; + uint64_t uint64_val1 = 10, uint64_val2 = 0; + std::string string_val1; + + librbd::image_options_create(&opts1); + ASSERT_NE((rbd_image_options_t)NULL, opts1); + ASSERT_TRUE(librbd::image_options_is_empty(opts1)); + + ASSERT_EQ(-EINVAL, librbd::image_options_get(opts1, RBD_IMAGE_OPTION_FEATURES, + &string_val1)); + ASSERT_EQ(-ENOENT, librbd::image_options_get(opts1, RBD_IMAGE_OPTION_FEATURES, + &uint64_val1)); + + ASSERT_EQ(-EINVAL, librbd::image_options_set(opts1, RBD_IMAGE_OPTION_FEATURES, + string_val1)); + + ASSERT_EQ(0, librbd::image_options_set(opts1, RBD_IMAGE_OPTION_FEATURES, + uint64_val1)); + ASSERT_FALSE(librbd::image_options_is_empty(opts1)); + ASSERT_EQ(0, librbd::image_options_get(opts1, RBD_IMAGE_OPTION_FEATURES, + &uint64_val2)); + ASSERT_EQ(uint64_val1, uint64_val2); + + librbd::image_options_create_ref(&opts2, opts1); + ASSERT_NE((rbd_image_options_t)NULL, opts2); + ASSERT_FALSE(librbd::image_options_is_empty(opts2)); + + uint64_val2 = 0; + ASSERT_NE(uint64_val1, uint64_val2); + ASSERT_EQ(0, librbd::image_options_get(opts2, RBD_IMAGE_OPTION_FEATURES, + &uint64_val2)); + ASSERT_EQ(uint64_val1, uint64_val2); + + uint64_val2++; + ASSERT_NE(uint64_val1, uint64_val2); + ASSERT_EQ(-ENOENT, librbd::image_options_get(opts1, RBD_IMAGE_OPTION_ORDER, + &uint64_val1)); + ASSERT_EQ(-ENOENT, librbd::image_options_get(opts2, RBD_IMAGE_OPTION_ORDER, + &uint64_val2)); + ASSERT_EQ(0, librbd::image_options_set(opts2, RBD_IMAGE_OPTION_ORDER, + uint64_val2)); + ASSERT_EQ(0, librbd::image_options_get(opts1, RBD_IMAGE_OPTION_ORDER, + &uint64_val1)); + ASSERT_EQ(0, librbd::image_options_get(opts2, RBD_IMAGE_OPTION_ORDER, + &uint64_val2)); + ASSERT_EQ(uint64_val1, uint64_val2); + + librbd::image_options_destroy(opts1); + + uint64_val2++; + ASSERT_NE(uint64_val1, uint64_val2); + ASSERT_EQ(0, librbd::image_options_get(opts2, RBD_IMAGE_OPTION_ORDER, + &uint64_val2)); + ASSERT_EQ(uint64_val1, uint64_val2); + + ASSERT_EQ(0, librbd::image_options_unset(opts2, RBD_IMAGE_OPTION_ORDER)); + ASSERT_EQ(-ENOENT, librbd::image_options_unset(opts2, RBD_IMAGE_OPTION_ORDER)); + + librbd::image_options_clear(opts2); + ASSERT_EQ(-ENOENT, librbd::image_options_get(opts2, RBD_IMAGE_OPTION_FEATURES, + &uint64_val2)); + ASSERT_TRUE(librbd::image_options_is_empty(opts2)); + + librbd::image_options_destroy(opts2); +} + +TEST_F(TestInternal, WriteFullCopyup) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, ictx->operations->resize(1 << ictx->order, true, no_op)); + + bufferlist bl; + bl.append(std::string(1 << ictx->order, '1')); + ASSERT_EQ((ssize_t)bl.length(), + ictx->io_work_queue->write(0, bl.length(), bufferlist{bl}, 0)); + ASSERT_EQ(0, ictx->io_work_queue->flush()); + + ASSERT_EQ(0, create_snapshot("snap1", true)); + + std::string clone_name = get_temp_image_name(); + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), ictx->features, &order, 0, 0)); + + TestInternal *parent = this; + librbd::ImageCtx *ictx2 = NULL; + BOOST_SCOPE_EXIT( (&m_ioctx) (clone_name) (parent) (&ictx2) ) { + if (ictx2 != NULL) { + ictx2->operations->snap_remove(cls::rbd::UserSnapshotNamespace(), + "snap1"); + parent->close_image(ictx2); + } + + librbd::NoOpProgressContext remove_no_op; + ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name, + remove_no_op)); + } BOOST_SCOPE_EXIT_END; + + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + ASSERT_EQ(0, ictx2->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap1")); + + bufferlist write_full_bl; + write_full_bl.append(std::string(1 << ictx2->order, '2')); + ASSERT_EQ((ssize_t)write_full_bl.length(), + ictx2->io_work_queue->write(0, write_full_bl.length(), + bufferlist{write_full_bl}, 0)); + + ASSERT_EQ(0, ictx2->operations->flatten(no_op)); + + bufferptr read_ptr(bl.length()); + bufferlist read_bl; + read_bl.push_back(read_ptr); + + librbd::io::ReadResult read_result{&read_bl}; + ASSERT_EQ((ssize_t)read_bl.length(), + ictx2->io_work_queue->read(0, read_bl.length(), + librbd::io::ReadResult{read_result}, 0)); + ASSERT_TRUE(write_full_bl.contents_equal(read_bl)); + + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx2, + cls::rbd::UserSnapshotNamespace(), + "snap1")); + ASSERT_EQ((ssize_t)read_bl.length(), + ictx2->io_work_queue->read(0, read_bl.length(), + librbd::io::ReadResult{read_result}, 0)); + ASSERT_TRUE(bl.contents_equal(read_bl)); +} + +static int iterate_cb(uint64_t off, size_t len, int exists, void *arg) +{ + interval_set<uint64_t> *diff = static_cast<interval_set<uint64_t> *>(arg); + diff->insert(off, len); + return 0; +} + +TEST_F(TestInternal, DiffIterateCloneOverwrite) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::RBD rbd; + librbd::Image image; + uint64_t size = 20 << 20; + int order = 0; + + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + + bufferlist bl; + bl.append(std::string(4096, '1')); + ASSERT_EQ(4096, image.write(0, 4096, bl)); + + interval_set<uint64_t> one; + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, false, false, iterate_cb, + (void *)&one)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + + std::string clone_name = this->get_temp_image_name(); + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "one")); + ASSERT_EQ(0, + ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "one")); + + // Simulate a client that doesn't support deep flatten (old librbd / krbd) + // which will copy up the full object from the parent + std::string oid = ictx->object_prefix + ".0000000000000000"; + librados::IoCtx io_ctx; + io_ctx.dup(m_ioctx); + io_ctx.selfmanaged_snap_set_write_ctx(ictx->snapc.seq, ictx->snaps); + ASSERT_EQ(0, io_ctx.write(oid, bl, 4096, 4096)); + + interval_set<uint64_t> diff; + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx, + cls::rbd::UserSnapshotNamespace(), + "one")); + ASSERT_EQ(0, librbd::api::DiffIterate<>::diff_iterate( + ictx, cls::rbd::UserSnapshotNamespace(), nullptr, 0, size, true, false, + iterate_cb, (void *)&diff)); + ASSERT_EQ(one, diff); +} + +TEST_F(TestInternal, TestCoR) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + std::string config_value; + ASSERT_EQ(0, _rados.conf_get("rbd_clone_copy_on_read", config_value)); + if (config_value == "false") { + std::cout << "SKIPPING due to disabled rbd_copy_on_read" << std::endl; + return; + } + + m_image_name = get_temp_image_name(); + m_image_size = 4 << 20; + + int order = 12; // smallest object size is 4K + uint64_t features; + ASSERT_TRUE(get_features(&features)); + + ASSERT_EQ(0, create_image_full_pp(m_rbd, m_ioctx, m_image_name, m_image_size, + features, false, &order)); + + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + + librbd::image_info_t info; + ASSERT_EQ(0, image.stat(info, sizeof(info))); + + const int object_num = info.size / info.obj_size; + printf("made parent image \"%s\": %ldK (%d * %" PRIu64 "K)\n", m_image_name.c_str(), + (unsigned long)m_image_size, object_num, info.obj_size/1024); + + // write something into parent + char test_data[TEST_IO_SIZE + 1]; + for (int i = 0; i < TEST_IO_SIZE; ++i) { + test_data[i] = (char) (rand() % (126 - 33) + 33); + } + test_data[TEST_IO_SIZE] = '\0'; + + // generate a random map which covers every objects with random + // offset + map<uint64_t, uint64_t> write_tracker; + generate_random_iomap(image, object_num, info.obj_size, 100, write_tracker); + + printf("generated random write map:\n"); + for (map<uint64_t, uint64_t>::iterator itr = write_tracker.begin(); + itr != write_tracker.end(); ++itr) + printf("\t [%-8lu, %-8lu]\n", + (unsigned long)itr->first, (unsigned long)itr->second); + + bufferlist bl; + bl.append(test_data, TEST_IO_SIZE); + + printf("write data based on random map\n"); + for (map<uint64_t, uint64_t>::iterator itr = write_tracker.begin(); + itr != write_tracker.end(); ++itr) { + printf("\twrite object-%-4lu\t\n", (unsigned long)itr->first); + ASSERT_EQ(TEST_IO_SIZE, image.write(itr->second, TEST_IO_SIZE, bl)); + } + + ASSERT_EQ(0, image.flush()); + + bufferlist readbl; + printf("verify written data by reading\n"); + { + map<uint64_t, uint64_t>::iterator itr = write_tracker.begin(); + printf("\tread object-%-4lu\n", (unsigned long)itr->first); + ASSERT_EQ(TEST_IO_SIZE, image.read(itr->second, TEST_IO_SIZE, readbl)); + ASSERT_TRUE(readbl.contents_equal(bl)); + } + + int64_t data_pool_id = image.get_data_pool_id(); + rados_ioctx_t d_ioctx; + ASSERT_EQ(0, rados_wait_for_latest_osdmap(_cluster)); + ASSERT_EQ(0, rados_ioctx_create2(_cluster, data_pool_id, &d_ioctx)); + + std::string block_name_prefix = image.get_block_name_prefix() + "."; + + const char *entry; + rados_list_ctx_t list_ctx; + set<string> obj_checker; + ASSERT_EQ(0, rados_nobjects_list_open(d_ioctx, &list_ctx)); + while (rados_nobjects_list_next(list_ctx, &entry, NULL, NULL) != -ENOENT) { + if (boost::starts_with(entry, block_name_prefix)) { + const char *block_name_suffix = entry + block_name_prefix.length(); + obj_checker.insert(block_name_suffix); + } + } + rados_nobjects_list_close(list_ctx); + + std::string snapname = "snap"; + std::string clonename = get_temp_image_name(); + ASSERT_EQ(0, image.snap_create(snapname.c_str())); + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, m_image_name.c_str(), snapname.c_str())); + ASSERT_EQ(0, image.snap_protect(snapname.c_str())); + printf("made snapshot \"%s@parent_snap\" and protect it\n", m_image_name.c_str()); + + ASSERT_EQ(0, clone_image_pp(m_rbd, image, m_ioctx, m_image_name.c_str(), snapname.c_str(), + m_ioctx, clonename.c_str(), features)); + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, clonename.c_str(), NULL)); + printf("made and opened clone \"%s\"\n", clonename.c_str()); + + printf("read from \"child\"\n"); + { + map<uint64_t, uint64_t>::iterator itr = write_tracker.begin(); + printf("\tread object-%-4lu\n", (unsigned long)itr->first); + ASSERT_EQ(TEST_IO_SIZE, image.read(itr->second, TEST_IO_SIZE, readbl)); + ASSERT_TRUE(readbl.contents_equal(bl)); + } + + for (map<uint64_t, uint64_t>::iterator itr = write_tracker.begin(); + itr != write_tracker.end(); ++itr) { + printf("\tread object-%-4lu\n", (unsigned long)itr->first); + ASSERT_EQ(TEST_IO_SIZE, image.read(itr->second, TEST_IO_SIZE, readbl)); + ASSERT_TRUE(readbl.contents_equal(bl)); + } + + printf("read again reversely\n"); + for (map<uint64_t, uint64_t>::iterator itr = --write_tracker.end(); + itr != write_tracker.begin(); --itr) { + printf("\tread object-%-4lu\n", (unsigned long)itr->first); + ASSERT_EQ(TEST_IO_SIZE, image.read(itr->second, TEST_IO_SIZE, readbl)); + ASSERT_TRUE(readbl.contents_equal(bl)); + } + + // close child to flush all copy-on-read + ASSERT_EQ(0, image.close()); + + printf("check whether child image has the same set of objects as parent\n"); + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, clonename.c_str(), NULL)); + block_name_prefix = image.get_block_name_prefix() + "."; + + ASSERT_EQ(0, rados_nobjects_list_open(d_ioctx, &list_ctx)); + while (rados_nobjects_list_next(list_ctx, &entry, NULL, NULL) != -ENOENT) { + if (boost::starts_with(entry, block_name_prefix)) { + const char *block_name_suffix = entry + block_name_prefix.length(); + set<string>::iterator it = obj_checker.find(block_name_suffix); + ASSERT_TRUE(it != obj_checker.end()); + obj_checker.erase(it); + } + } + rados_nobjects_list_close(list_ctx); + ASSERT_TRUE(obj_checker.empty()); + ASSERT_EQ(0, image.close()); + + rados_ioctx_destroy(d_ioctx); +} + +TEST_F(TestInternal, FlattenNoEmptyObjects) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + m_image_name = get_temp_image_name(); + m_image_size = 4 << 20; + + int order = 12; // smallest object size is 4K + uint64_t features; + ASSERT_TRUE(get_features(&features)); + + ASSERT_EQ(0, create_image_full_pp(m_rbd, m_ioctx, m_image_name, m_image_size, + features, false, &order)); + + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + + librbd::image_info_t info; + ASSERT_EQ(0, image.stat(info, sizeof(info))); + + const int object_num = info.size / info.obj_size; + printf("made parent image \"%s\": %" PRIu64 "K (%d * %" PRIu64 "K)\n", + m_image_name.c_str(), m_image_size, object_num, info.obj_size/1024); + + // write something into parent + char test_data[TEST_IO_SIZE + 1]; + for (int i = 0; i < TEST_IO_SIZE; ++i) { + test_data[i] = (char) (rand() % (126 - 33) + 33); + } + test_data[TEST_IO_SIZE] = '\0'; + + // generate a random map which covers every objects with random + // offset + map<uint64_t, uint64_t> write_tracker; + generate_random_iomap(image, object_num, info.obj_size, 100, write_tracker); + + printf("generated random write map:\n"); + for (map<uint64_t, uint64_t>::iterator itr = write_tracker.begin(); + itr != write_tracker.end(); ++itr) + printf("\t [%-8lu, %-8lu]\n", + (unsigned long)itr->first, (unsigned long)itr->second); + + bufferlist bl; + bl.append(test_data, TEST_IO_SIZE); + + printf("write data based on random map\n"); + for (map<uint64_t, uint64_t>::iterator itr = write_tracker.begin(); + itr != write_tracker.end(); ++itr) { + printf("\twrite object-%-4lu\t\n", (unsigned long)itr->first); + ASSERT_EQ(TEST_IO_SIZE, image.write(itr->second, TEST_IO_SIZE, bl)); + } + + ASSERT_EQ(0, image.flush()); + + bufferlist readbl; + printf("verify written data by reading\n"); + { + map<uint64_t, uint64_t>::iterator itr = write_tracker.begin(); + printf("\tread object-%-4lu\n", (unsigned long)itr->first); + ASSERT_EQ(TEST_IO_SIZE, image.read(itr->second, TEST_IO_SIZE, readbl)); + ASSERT_TRUE(readbl.contents_equal(bl)); + } + + int64_t data_pool_id = image.get_data_pool_id(); + rados_ioctx_t d_ioctx; + ASSERT_EQ(0, rados_wait_for_latest_osdmap(_cluster)); + ASSERT_EQ(0, rados_ioctx_create2(_cluster, data_pool_id, &d_ioctx)); + + std::string block_name_prefix = image.get_block_name_prefix() + "."; + + const char *entry; + rados_list_ctx_t list_ctx; + set<string> obj_checker; + ASSERT_EQ(0, rados_nobjects_list_open(d_ioctx, &list_ctx)); + while (rados_nobjects_list_next(list_ctx, &entry, NULL, NULL) != -ENOENT) { + if (boost::starts_with(entry, block_name_prefix)) { + const char *block_name_suffix = entry + block_name_prefix.length(); + obj_checker.insert(block_name_suffix); + } + } + rados_nobjects_list_close(list_ctx); + + std::string snapname = "snap"; + std::string clonename = get_temp_image_name(); + ASSERT_EQ(0, image.snap_create(snapname.c_str())); + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, m_image_name.c_str(), snapname.c_str())); + ASSERT_EQ(0, image.snap_protect(snapname.c_str())); + printf("made snapshot \"%s@parent_snap\" and protect it\n", m_image_name.c_str()); + + ASSERT_EQ(0, clone_image_pp(m_rbd, image, m_ioctx, m_image_name.c_str(), snapname.c_str(), + m_ioctx, clonename.c_str(), features)); + ASSERT_EQ(0, image.close()); + + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, clonename.c_str(), NULL)); + printf("made and opened clone \"%s\"\n", clonename.c_str()); + + printf("flattening clone: \"%s\"\n", clonename.c_str()); + ASSERT_EQ(0, image.flatten()); + + printf("check whether child image has the same set of objects as parent\n"); + block_name_prefix = image.get_block_name_prefix() + "."; + + ASSERT_EQ(0, rados_nobjects_list_open(d_ioctx, &list_ctx)); + while (rados_nobjects_list_next(list_ctx, &entry, NULL, NULL) != -ENOENT) { + if (boost::starts_with(entry, block_name_prefix)) { + const char *block_name_suffix = entry + block_name_prefix.length(); + set<string>::iterator it = obj_checker.find(block_name_suffix); + ASSERT_TRUE(it != obj_checker.end()); + obj_checker.erase(it); + } + } + rados_nobjects_list_close(list_ctx); + ASSERT_TRUE(obj_checker.empty()); + ASSERT_EQ(0, image.close()); + + rados_ioctx_destroy(d_ioctx); +} + +TEST_F(TestInternal, PoolMetadataConfApply) { + REQUIRE_FORMAT_V2(); + + librbd::api::PoolMetadata<>::remove(m_ioctx, "conf_rbd_cache"); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + bool cache = ictx->cache; + std::string rbd_conf_cache = cache ? "true" : "false"; + std::string new_rbd_conf_cache = !cache ? "true" : "false"; + + ASSERT_EQ(0, librbd::api::PoolMetadata<>::set(m_ioctx, "conf_rbd_cache", + new_rbd_conf_cache)); + ASSERT_EQ(0, ictx->state->refresh()); + ASSERT_EQ(!cache, ictx->cache); + + ASSERT_EQ(0, ictx->operations->metadata_set("conf_rbd_cache", + rbd_conf_cache)); + ASSERT_EQ(cache, ictx->cache); + + ASSERT_EQ(0, ictx->operations->metadata_remove("conf_rbd_cache")); + ASSERT_EQ(!cache, ictx->cache); + + ASSERT_EQ(0, librbd::api::PoolMetadata<>::remove(m_ioctx, "conf_rbd_cache")); + ASSERT_EQ(0, ictx->state->refresh()); + ASSERT_EQ(cache, ictx->cache); + close_image(ictx); + + ASSERT_EQ(0, librbd::api::PoolMetadata<>::set(m_ioctx, + "conf_rbd_default_order", + "17")); + ASSERT_EQ(0, librbd::api::PoolMetadata<>::set(m_ioctx, + "conf_rbd_journal_order", + "13")); + std::string image_name = get_temp_image_name(); + int order = 0; + uint64_t features; + ASSERT_TRUE(get_features(&features)); + ASSERT_EQ(0, create_image_full_pp(m_rbd, m_ioctx, image_name, m_image_size, + features, false, &order)); + + ASSERT_EQ(0, open_image(image_name, &ictx)); + ASSERT_EQ(ictx->order, 17); + ASSERT_EQ(ictx->config.get_val<uint64_t>("rbd_journal_order"), 13U); + + if (is_feature_enabled(RBD_FEATURE_JOURNALING)) { + uint8_t order; + uint8_t splay_width; + int64_t pool_id; + C_SaferCond cond; + cls::journal::client::get_immutable_metadata(m_ioctx, "journal." + ictx->id, + &order, &splay_width, &pool_id, + &cond); + ASSERT_EQ(0, cond.wait()); + ASSERT_EQ(order, 13); + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + ASSERT_EQ(0, librbd::api::PoolMetadata<>::set(m_ioctx, + "conf_rbd_journal_order", + "14")); + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + true)); + ASSERT_EQ(ictx->config.get_val<uint64_t>("rbd_journal_order"), 14U); + + C_SaferCond cond1; + cls::journal::client::get_immutable_metadata(m_ioctx, "journal." + ictx->id, + &order, &splay_width, &pool_id, + &cond1); + ASSERT_EQ(0, cond1.wait()); + ASSERT_EQ(order, 14); + } + + ASSERT_EQ(0, librbd::api::PoolMetadata<>::remove(m_ioctx, + "conf_rbd_default_order")); + ASSERT_EQ(0, librbd::api::PoolMetadata<>::remove(m_ioctx, + "conf_rbd_journal_order")); +} + +TEST_F(TestInternal, Sparsify) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + bool sparsify_supported = is_sparsify_supported(ictx->data_ctx, + ictx->get_object_name(10)); + bool sparse_read_supported = is_sparse_read_supported( + ictx->data_ctx, ictx->get_object_name(10)); + + std::cout << "sparsify_supported=" << sparsify_supported << std::endl; + std::cout << "sparse_read_supported=" << sparse_read_supported << std::endl; + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, ictx->operations->resize((1 << ictx->order) * 20, true, no_op)); + + bufferlist bl; + bl.append(std::string(4096, '\0')); + + ASSERT_EQ((ssize_t)bl.length(), + ictx->io_work_queue->write(0, bl.length(), bufferlist{bl}, 0)); + + ASSERT_EQ((ssize_t)bl.length(), + ictx->io_work_queue->write((1 << ictx->order) * 1 + 512, + bl.length(), bufferlist{bl}, 0)); + + bl.append(std::string(4096, '1')); + bl.append(std::string(4096, '\0')); + bl.append(std::string(4096, '2')); + bl.append(std::string(4096 - 1, '\0')); + ASSERT_EQ((ssize_t)bl.length(), + ictx->io_work_queue->write((1 << ictx->order) * 10, bl.length(), + bufferlist{bl}, 0)); + + bufferlist bl2; + bl2.append(std::string(4096 - 1, '\0')); + ASSERT_EQ((ssize_t)bl2.length(), + ictx->io_work_queue->write((1 << ictx->order) * 10 + 4096 * 10, + bl2.length(), bufferlist{bl2}, 0)); + + ASSERT_EQ(0, ictx->io_work_queue->flush()); + + ASSERT_EQ(0, ictx->operations->sparsify(4096, no_op)); + + bufferptr read_ptr(bl.length()); + bufferlist read_bl; + read_bl.push_back(read_ptr); + + librbd::io::ReadResult read_result{&read_bl}; + ASSERT_EQ((ssize_t)read_bl.length(), + ictx->io_work_queue->read((1 << ictx->order) * 10, read_bl.length(), + librbd::io::ReadResult{read_result}, 0)); + ASSERT_TRUE(bl.contents_equal(read_bl)); + + std::string oid = ictx->get_object_name(0); + uint64_t size; + ASSERT_EQ(-ENOENT, ictx->data_ctx.stat(oid, &size, NULL)); + + oid = ictx->get_object_name(1); + ASSERT_EQ(-ENOENT, ictx->data_ctx.stat(oid, &size, NULL)); + + oid = ictx->get_object_name(10); + std::map<uint64_t, uint64_t> m; + std::map<uint64_t, uint64_t> expected_m; + auto read_len = bl.length(); + bl.clear(); + if (sparsify_supported && sparse_read_supported) { + expected_m = {{4096 * 1, 4096}, {4096 * 3, 4096}}; + bl.append(std::string(4096, '1')); + bl.append(std::string(4096, '2')); + } else { + expected_m = {{0, 4096 * 4}}; + bl.append(std::string(4096, '\0')); + bl.append(std::string(4096, '1')); + bl.append(std::string(4096, '\0')); + bl.append(std::string(4096, '2')); + } + read_bl.clear(); + EXPECT_EQ(static_cast<int>(expected_m.size()), + ictx->data_ctx.sparse_read(oid, m, read_bl, read_len, 0)); + EXPECT_EQ(m, expected_m); + EXPECT_TRUE(bl.contents_equal(read_bl)); +} + + +TEST_F(TestInternal, SparsifyClone) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + bool sparsify_supported = is_sparsify_supported(ictx->data_ctx, + ictx->get_object_name(10)); + std::cout << "sparsify_supported=" << sparsify_supported << std::endl; + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, ictx->operations->resize((1 << ictx->order) * 10, true, no_op)); + + ASSERT_EQ(0, create_snapshot("snap", true)); + std::string clone_name = get_temp_image_name(); + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap", m_ioctx, + clone_name.c_str(), ictx->features, &order, 0, 0)); + close_image(ictx); + + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + BOOST_SCOPE_EXIT_ALL(this, &ictx, clone_name) { + close_image(ictx); + librbd::NoOpProgressContext no_op; + EXPECT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name, no_op)); + }; + + ASSERT_EQ(0, ictx->operations->resize((1 << ictx->order) * 20, true, no_op)); + + bufferlist bl; + bl.append(std::string(4096, '\0')); + + ASSERT_EQ((ssize_t)bl.length(), + ictx->io_work_queue->write(0, bl.length(), bufferlist{bl}, 0)); + + bl.append(std::string(4096, '1')); + bl.append(std::string(4096, '\0')); + bl.append(std::string(4096, '2')); + bl.append(std::string(4096, '\0')); + ASSERT_EQ((ssize_t)bl.length(), + ictx->io_work_queue->write((1 << ictx->order) * 10, bl.length(), + bufferlist{bl}, 0)); + ASSERT_EQ(0, ictx->io_work_queue->flush()); + + ASSERT_EQ(0, ictx->operations->sparsify(4096, no_op)); + + bufferptr read_ptr(bl.length()); + bufferlist read_bl; + read_bl.push_back(read_ptr); + + librbd::io::ReadResult read_result{&read_bl}; + ASSERT_EQ((ssize_t)read_bl.length(), + ictx->io_work_queue->read((1 << ictx->order) * 10, read_bl.length(), + librbd::io::ReadResult{read_result}, 0)); + ASSERT_TRUE(bl.contents_equal(read_bl)); + + std::string oid = ictx->get_object_name(0); + uint64_t size; + ASSERT_EQ(0, ictx->data_ctx.stat(oid, &size, NULL)); + ASSERT_EQ(0, ictx->data_ctx.read(oid, read_bl, 4096, 0)); +} + +TEST_F(TestInternal, MissingDataPool) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + std::string header_oid = ictx->header_oid; + close_image(ictx); + + // emulate non-existent data pool + int64_t pool_id = 1234; + std::string pool_name; + int r; + while ((r = _rados.pool_reverse_lookup(pool_id, &pool_name)) == 0) { + pool_id++; + } + ASSERT_EQ(r, -ENOENT); + bufferlist bl; + using ceph::encode; + encode(pool_id, bl); + ASSERT_EQ(0, m_ioctx.omap_set(header_oid, {{"data_pool_id", bl}})); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_FALSE(ictx->data_ctx.is_valid()); + ASSERT_EQ(pool_id, librbd::api::Image<>::get_data_pool_id(ictx)); + + librbd::image_info_t info; + ASSERT_EQ(0, librbd::info(ictx, info, sizeof(info))); + + vector<librbd::snap_info_t> snaps; + EXPECT_EQ(0, librbd::snap_list(ictx, snaps)); + EXPECT_EQ(1U, snaps.size()); + EXPECT_EQ("snap1", snaps[0].name); + + bufferptr read_ptr(256); + bufferlist read_bl; + read_bl.push_back(read_ptr); + librbd::io::ReadResult read_result{&read_bl}; + ASSERT_EQ(-ENODEV, + ictx->io_work_queue->read(0, 256, + librbd::io::ReadResult{read_result}, 0)); + ASSERT_EQ(-ENODEV, + ictx->io_work_queue->write(0, bl.length(), bufferlist{bl}, 0)); + ASSERT_EQ(-ENODEV, ictx->io_work_queue->discard(0, 1, 256)); + ASSERT_EQ(-ENODEV, + ictx->io_work_queue->writesame(0, bl.length(), bufferlist{bl}, 0)); + uint64_t mismatch_off; + ASSERT_EQ(-ENODEV, + ictx->io_work_queue->compare_and_write(0, bl.length(), + bufferlist{bl}, + bufferlist{bl}, + &mismatch_off, 0)); + ASSERT_EQ(-ENODEV, ictx->io_work_queue->flush()); + + ASSERT_EQ(-ENODEV, snap_create(*ictx, "snap2")); + ASSERT_EQ(0, ictx->operations->snap_remove(cls::rbd::UserSnapshotNamespace(), + "snap1")); + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(-ENODEV, ictx->operations->resize(0, true, no_op)); + + close_image(ictx); + + ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, m_image_name, no_op)); + + ASSERT_EQ(0, create_image_pp(m_rbd, m_ioctx, m_image_name, m_image_size)); +} diff --git a/src/test/librbd/test_librbd.cc b/src/test/librbd/test_librbd.cc new file mode 100644 index 00000000..25a258f6 --- /dev/null +++ b/src/test/librbd/test_librbd.cc @@ -0,0 +1,8056 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License version 2, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "include/int_types.h" +#include "include/rados/librados.h" +#include "include/rbd_types.h" +#include "include/rbd/librbd.h" +#include "include/rbd/librbd.hpp" +#include "include/event_type.h" +#include "include/err.h" + +#include "gtest/gtest.h" + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <poll.h> +#include <time.h> +#include <unistd.h> +#include <algorithm> +#include <chrono> +#include <condition_variable> +#include <iostream> +#include <sstream> +#include <list> +#include <set> +#include <thread> +#include <vector> + +#include "test/librados/test.h" +#include "test/librados/test_cxx.h" +#include "test/librbd/test_support.h" +#include "common/event_socket.h" +#include "include/interval_set.h" +#include "include/stringify.h" + +#include <boost/assign/list_of.hpp> +#include <boost/scope_exit.hpp> + +#ifdef HAVE_EVENTFD +#include <sys/eventfd.h> +#endif + +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +using namespace std; + +using std::chrono::seconds; + +#define ASSERT_PASSED(x, args...) \ + do { \ + bool passed = false; \ + x(args, &passed); \ + ASSERT_TRUE(passed); \ + } while(0) + +void register_test_librbd() { +} + +static int get_features(bool *old_format, uint64_t *features) +{ + const char *c = getenv("RBD_FEATURES"); + if (c && strlen(c) > 0) { + stringstream ss; + ss << c; + ss >> *features; + if (ss.fail()) + return -EINVAL; + *old_format = false; + cout << "using new format!" << std::endl; + } else { + *old_format = true; + *features = 0; + cout << "using old format" << std::endl; + } + + return 0; +} + +static int create_image_full(rados_ioctx_t ioctx, const char *name, + uint64_t size, int *order, int old_format, + uint64_t features) +{ + if (old_format) { + // ensure old-format tests actually use the old format + int r = rados_conf_set(rados_ioctx_get_cluster(ioctx), + "rbd_default_format", "1"); + if (r < 0) { + return r; + } + return rbd_create(ioctx, name, size, order); + } else if ((features & RBD_FEATURE_STRIPINGV2) != 0) { + uint64_t stripe_unit = IMAGE_STRIPE_UNIT; + if (*order) { + // use a conservative stripe_unit for non default order + stripe_unit = (1ull << (*order-1)); + } + + printf("creating image with stripe unit: %" PRIu64 ", " + "stripe count: %" PRIu64 "\n", + stripe_unit, IMAGE_STRIPE_COUNT); + return rbd_create3(ioctx, name, size, features, order, + stripe_unit, IMAGE_STRIPE_COUNT); + } else { + return rbd_create2(ioctx, name, size, features, order); + } +} + +static int clone_image(rados_ioctx_t p_ioctx, + rbd_image_t p_image, const char *p_name, + const char *p_snap_name, rados_ioctx_t c_ioctx, + const char *c_name, uint64_t features, int *c_order) +{ + uint64_t stripe_unit, stripe_count; + + int r; + r = rbd_get_stripe_unit(p_image, &stripe_unit); + if (r != 0) { + return r; + } + + r = rbd_get_stripe_count(p_image, &stripe_count); + if (r != 0) { + return r; + } + + return rbd_clone2(p_ioctx, p_name, p_snap_name, c_ioctx, + c_name, features, c_order, stripe_unit, stripe_count); +} + + +static int create_image(rados_ioctx_t ioctx, const char *name, + uint64_t size, int *order) +{ + bool old_format; + uint64_t features; + + int r = get_features(&old_format, &features); + if (r < 0) + return r; + return create_image_full(ioctx, name, size, order, old_format, features); +} + +static int create_image_pp(librbd::RBD &rbd, + librados::IoCtx &ioctx, + const char *name, + uint64_t size, int *order) { + bool old_format; + uint64_t features; + int r = get_features(&old_format, &features); + if (r < 0) + return r; + if (old_format) { + librados::Rados rados(ioctx); + int r = rados.conf_set("rbd_default_format", "1"); + if (r < 0) { + return r; + } + return rbd.create(ioctx, name, size, order); + } else { + return rbd.create2(ioctx, name, size, features, order); + } +} + +class TestLibRBD : public ::testing::Test { +public: + + TestLibRBD() : m_pool_number() { + } + + static void SetUpTestCase() { + _pool_names.clear(); + _unique_pool_names.clear(); + _image_number = 0; + ASSERT_EQ("", connect_cluster(&_cluster)); + ASSERT_EQ("", connect_cluster_pp(_rados)); + + create_optional_data_pool(); + } + + static void TearDownTestCase() { + rados_shutdown(_cluster); + _rados.wait_for_latest_osdmap(); + _pool_names.insert(_pool_names.end(), _unique_pool_names.begin(), + _unique_pool_names.end()); + for (size_t i = 1; i < _pool_names.size(); ++i) { + ASSERT_EQ(0, _rados.pool_delete(_pool_names[i].c_str())); + } + if (!_pool_names.empty()) { + ASSERT_EQ(0, destroy_one_pool_pp(_pool_names[0], _rados)); + } + } + + void SetUp() override { + ASSERT_NE("", m_pool_name = create_pool()); + } + + bool is_skip_partial_discard_enabled() { + std::string value; + EXPECT_EQ(0, _rados.conf_get("rbd_skip_partial_discard", value)); + return value == "true"; + } + + void validate_object_map(rbd_image_t image, bool *passed) { + uint64_t flags; + ASSERT_EQ(0, rbd_get_flags(image, &flags)); + *passed = ((flags & RBD_FLAG_OBJECT_MAP_INVALID) == 0); + } + + void validate_object_map(librbd::Image &image, bool *passed) { + uint64_t flags; + ASSERT_EQ(0, image.get_flags(&flags)); + *passed = ((flags & RBD_FLAG_OBJECT_MAP_INVALID) == 0); + } + + static std::string get_temp_image_name() { + ++_image_number; + return "image" + stringify(_image_number); + } + + static void create_optional_data_pool() { + bool created = false; + std::string data_pool; + ASSERT_EQ(0, create_image_data_pool(_rados, data_pool, &created)); + if (!data_pool.empty()) { + printf("using image data pool: %s\n", data_pool.c_str()); + if (created) { + _unique_pool_names.push_back(data_pool); + } + } + } + + std::string create_pool(bool unique = false) { + librados::Rados rados; + std::string pool_name; + if (unique) { + pool_name = get_temp_pool_name("test-librbd-"); + EXPECT_EQ("", create_one_pool_pp(pool_name, rados)); + _unique_pool_names.push_back(pool_name); + } else if (m_pool_number < _pool_names.size()) { + pool_name = _pool_names[m_pool_number]; + } else { + pool_name = get_temp_pool_name("test-librbd-"); + EXPECT_EQ("", create_one_pool_pp(pool_name, rados)); + _pool_names.push_back(pool_name); + } + ++m_pool_number; + return pool_name; + } + + static std::vector<std::string> _pool_names; + static std::vector<std::string> _unique_pool_names; + static rados_t _cluster; + static librados::Rados _rados; + static uint64_t _image_number; + + std::string m_pool_name; + uint32_t m_pool_number; + +}; + +std::vector<std::string> TestLibRBD::_pool_names; +std::vector<std::string> TestLibRBD::_unique_pool_names; +rados_t TestLibRBD::_cluster; +librados::Rados TestLibRBD::_rados; +uint64_t TestLibRBD::_image_number = 0; + +TEST_F(TestLibRBD, CreateAndStat) +{ + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + + rbd_image_info_t info; + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + printf("image has size %llu and order %d\n", (unsigned long long) info.size, info.order); + ASSERT_EQ(info.size, size); + ASSERT_EQ(info.order, order); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, CreateWithSameDataPool) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + + rbd_image_t image; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + bool old_format; + uint64_t features; + ASSERT_EQ(0, get_features(&old_format, &features)); + ASSERT_FALSE(old_format); + + rbd_image_options_t image_options; + rbd_image_options_create(&image_options); + BOOST_SCOPE_EXIT( (&image_options) ) { + rbd_image_options_destroy(image_options); + } BOOST_SCOPE_EXIT_END; + + ASSERT_EQ(0, rbd_image_options_set_uint64(image_options, + RBD_IMAGE_OPTION_FEATURES, + features)); + ASSERT_EQ(0, rbd_image_options_set_string(image_options, + RBD_IMAGE_OPTION_DATA_POOL, + m_pool_name.c_str())); + + ASSERT_EQ(0, rbd_create4(ioctx, name.c_str(), size, image_options)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, CreateAndStatPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::image_info_t info; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + ASSERT_EQ(0, image.stat(info, sizeof(info))); + ASSERT_EQ(info.size, size); + ASSERT_EQ(info.order, order); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, GetId) +{ + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), 0, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + char id[4096]; + if (!is_feature_enabled(0)) { + // V1 image + ASSERT_EQ(-EINVAL, rbd_get_id(image, id, sizeof(id))); + } else { + ASSERT_EQ(-ERANGE, rbd_get_id(image, id, 0)); + ASSERT_EQ(0, rbd_get_id(image, id, sizeof(id))); + ASSERT_LT(0U, strlen(id)); + + ASSERT_EQ(0, rbd_close(image)); + ASSERT_EQ(0, rbd_open_by_id(ioctx, id, &image, NULL)); + size_t name_len = 0; + ASSERT_EQ(-ERANGE, rbd_get_name(image, NULL, &name_len)); + ASSERT_EQ(name_len, name.size() + 1); + char image_name[name_len]; + ASSERT_EQ(0, rbd_get_name(image, image_name, &name_len)); + ASSERT_STREQ(name.c_str(), image_name); + } + + ASSERT_EQ(0, rbd_close(image)); + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, GetIdPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + + std::string id; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), 0, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + if (!is_feature_enabled(0)) { + // V1 image + ASSERT_EQ(-EINVAL, image.get_id(&id)); + } else { + ASSERT_EQ(0, image.get_id(&id)); + ASSERT_LT(0U, id.size()); + + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, rbd.open_by_id(ioctx, image, id.c_str(), NULL)); + std::string image_name; + ASSERT_EQ(0, image.get_name(&image_name)); + ASSERT_EQ(name, image_name); + } +} + +TEST_F(TestLibRBD, GetBlockNamePrefix) +{ + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), 0, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + char prefix[4096]; + ASSERT_EQ(-ERANGE, rbd_get_block_name_prefix(image, prefix, 0)); + ASSERT_EQ(0, rbd_get_block_name_prefix(image, prefix, sizeof(prefix))); + ASSERT_LT(0U, strlen(prefix)); + + ASSERT_EQ(0, rbd_close(image)); + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, GetBlockNamePrefixPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), 0, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + ASSERT_LT(0U, image.get_block_name_prefix().size()); +} + +TEST_F(TestLibRBD, TestGetCreateTimestamp) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), 0, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + struct timespec timestamp; + ASSERT_EQ(0, rbd_get_create_timestamp(image, ×tamp)); + ASSERT_LT(0, timestamp.tv_sec); + + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, GetCreateTimestampPP) +{ + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), 0, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + struct timespec timestamp; + ASSERT_EQ(0, image.get_create_timestamp(×tamp)); + ASSERT_LT(0, timestamp.tv_sec); +} + +TEST_F(TestLibRBD, OpenAio) +{ + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + + rbd_image_info_t info; + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + + rbd_completion_t open_comp; + ASSERT_EQ(0, rbd_aio_create_completion(NULL, NULL, &open_comp)); + ASSERT_EQ(0, rbd_aio_open(ioctx, name.c_str(), &image, NULL, open_comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(open_comp)); + ASSERT_EQ(1, rbd_aio_is_complete(open_comp)); + ASSERT_EQ(0, rbd_aio_get_return_value(open_comp)); + rbd_aio_release(open_comp); + + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + printf("image has size %llu and order %d\n", (unsigned long long) info.size, info.order); + ASSERT_EQ(info.size, size); + ASSERT_EQ(info.order, order); + + rbd_completion_t close_comp; + ASSERT_EQ(0, rbd_aio_create_completion(NULL, NULL, &close_comp)); + ASSERT_EQ(0, rbd_aio_close(image, close_comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(close_comp)); + ASSERT_EQ(1, rbd_aio_is_complete(close_comp)); + ASSERT_EQ(0, rbd_aio_get_return_value(close_comp)); + rbd_aio_release(close_comp); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, OpenAioFail) +{ + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + + std::string name = get_temp_image_name(); + rbd_image_t image; + rbd_completion_t open_comp; + ASSERT_EQ(0, rbd_aio_create_completion(NULL, NULL, &open_comp)); + ASSERT_EQ(0, rbd_aio_open(ioctx, name.c_str(), &image, NULL, open_comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(open_comp)); + ASSERT_EQ(1, rbd_aio_is_complete(open_comp)); + ASSERT_EQ(-ENOENT, rbd_aio_get_return_value(open_comp)); + rbd_aio_release(open_comp); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, OpenAioPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::image_info_t info; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::RBD::AioCompletion *open_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, rbd.aio_open(ioctx, image, name.c_str(), NULL, open_comp)); + ASSERT_EQ(0, open_comp->wait_for_complete()); + ASSERT_EQ(1, open_comp->is_complete()); + ASSERT_EQ(0, open_comp->get_return_value()); + open_comp->release(); + + ASSERT_EQ(0, image.stat(info, sizeof(info))); + ASSERT_EQ(info.size, size); + ASSERT_EQ(info.order, order); + + // reopen + open_comp = new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, rbd.aio_open(ioctx, image, name.c_str(), NULL, open_comp)); + ASSERT_EQ(0, open_comp->wait_for_complete()); + ASSERT_EQ(1, open_comp->is_complete()); + ASSERT_EQ(0, open_comp->get_return_value()); + open_comp->release(); + + // close + librbd::RBD::AioCompletion *close_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, image.aio_close(close_comp)); + ASSERT_EQ(0, close_comp->wait_for_complete()); + ASSERT_EQ(1, close_comp->is_complete()); + ASSERT_EQ(0, close_comp->get_return_value()); + close_comp->release(); + + // close closed image + close_comp = new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(-EINVAL, image.aio_close(close_comp)); + close_comp->release(); + + ioctx.close(); +} + +TEST_F(TestLibRBD, OpenAioFailPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + std::string name = get_temp_image_name(); + + librbd::RBD::AioCompletion *open_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, rbd.aio_open(ioctx, image, name.c_str(), NULL, open_comp)); + ASSERT_EQ(0, open_comp->wait_for_complete()); + ASSERT_EQ(1, open_comp->is_complete()); + ASSERT_EQ(-ENOENT, open_comp->get_return_value()); + open_comp->release(); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, ResizeAndStat) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_info_t info; + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_resize(image, size * 4)); + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + ASSERT_EQ(info.size, size * 4); + + ASSERT_EQ(0, rbd_resize(image, size / 2)); + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + ASSERT_EQ(info.size, size / 2); + + // downsizing without allowing shrink should fail + // and image size should not change + ASSERT_EQ(-EINVAL, rbd_resize2(image, size / 4, false, NULL, NULL)); + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + ASSERT_EQ(info.size, size / 2); + + ASSERT_EQ(0, rbd_resize2(image, size / 4, true, NULL, NULL)); + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + ASSERT_EQ(info.size, size / 4); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, ResizeAndStatPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::image_info_t info; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + ASSERT_EQ(0, image.resize(size * 4)); + ASSERT_EQ(0, image.stat(info, sizeof(info))); + ASSERT_EQ(info.size, size * 4); + + ASSERT_EQ(0, image.resize(size / 2)); + ASSERT_EQ(0, image.stat(info, sizeof(info))); + ASSERT_EQ(info.size, size / 2); + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, UpdateWatchAndResize) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + struct Watcher { + rbd_image_t &m_image; + mutex m_lock; + condition_variable m_cond; + size_t m_size = 0; + static void cb(void *arg) { + Watcher *watcher = static_cast<Watcher *>(arg); + watcher->handle_notify(); + } + explicit Watcher(rbd_image_t &image) : m_image(image) {} + void handle_notify() { + rbd_image_info_t info; + ASSERT_EQ(0, rbd_stat(m_image, &info, sizeof(info))); + lock_guard<mutex> locker(m_lock); + m_size = info.size; + m_cond.notify_one(); + } + void wait_for_size(size_t size) { + unique_lock<mutex> locker(m_lock); + ASSERT_TRUE(m_cond.wait_for(locker, seconds(5), + [size, this] { + return this->m_size == size;})); + } + } watcher(image); + uint64_t handle; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_update_watch(image, &handle, Watcher::cb, &watcher)); + + ASSERT_EQ(0, rbd_resize(image, size * 4)); + watcher.wait_for_size(size * 4); + + ASSERT_EQ(0, rbd_resize(image, size / 2)); + watcher.wait_for_size(size / 2); + + ASSERT_EQ(0, rbd_update_unwatch(image, handle)); + + ASSERT_EQ(0, rbd_close(image)); + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, UpdateWatchAndResizePP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + struct Watcher : public librbd::UpdateWatchCtx { + explicit Watcher(librbd::Image &image) : m_image(image) { + } + void handle_notify() override { + librbd::image_info_t info; + ASSERT_EQ(0, m_image.stat(info, sizeof(info))); + lock_guard<mutex> locker(m_lock); + m_size = info.size; + m_cond.notify_one(); + } + void wait_for_size(size_t size) { + unique_lock<mutex> locker(m_lock); + ASSERT_TRUE(m_cond.wait_for(locker, seconds(5), + [size, this] { + return this->m_size == size;})); + } + librbd::Image &m_image; + mutex m_lock; + condition_variable m_cond; + size_t m_size = 0; + } watcher(image); + uint64_t handle; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + ASSERT_EQ(0, image.update_watch(&watcher, &handle)); + + ASSERT_EQ(0, image.resize(size * 4)); + watcher.wait_for_size(size * 4); + + ASSERT_EQ(0, image.resize(size / 2)); + watcher.wait_for_size(size / 2); + + ASSERT_EQ(0, image.update_unwatch(handle)); + } + + ioctx.close(); +} + +int test_ls(rados_ioctx_t io_ctx, size_t num_expected, ...) +{ + int num_images, i; + char *names, *cur_name; + va_list ap; + size_t max_size = 1024; + + names = (char *) malloc(sizeof(char) * 1024); + int len = rbd_list(io_ctx, names, &max_size); + + std::set<std::string> image_names; + for (i = 0, num_images = 0, cur_name = names; cur_name < names + len; i++) { + printf("image: %s\n", cur_name); + image_names.insert(cur_name); + cur_name += strlen(cur_name) + 1; + num_images++; + } + free(names); + + va_start(ap, num_expected); + for (i = num_expected; i > 0; i--) { + char *expected = va_arg(ap, char *); + printf("expected = %s\n", expected); + std::set<std::string>::iterator it = image_names.find(expected); + if (it != image_names.end()) { + printf("found %s\n", expected); + image_names.erase(it); + printf("erased %s\n", expected); + } else { + ADD_FAILURE() << "Unable to find image " << expected; + va_end(ap); + return -ENOENT; + } + } + va_end(ap); + + if (!image_names.empty()) { + ADD_FAILURE() << "Unexpected images discovered"; + return -EINVAL; + } + return num_images; +} + +TEST_F(TestLibRBD, TestCreateLsDelete) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, create_pool(true).c_str(), &ioctx); + + int order = 0; + std::string name = get_temp_image_name(); + std::string name2 = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, test_ls(ioctx, 0)); + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(1, test_ls(ioctx, 1, name.c_str())); + ASSERT_EQ(0, create_image(ioctx, name2.c_str(), size, &order)); + ASSERT_EQ(2, test_ls(ioctx, 2, name.c_str(), name2.c_str())); + ASSERT_EQ(0, rbd_remove(ioctx, name.c_str())); + ASSERT_EQ(1, test_ls(ioctx, 1, name2.c_str())); + + ASSERT_EQ(-ENOENT, rbd_remove(ioctx, name.c_str())); + + rados_ioctx_destroy(ioctx); +} + +int test_ls_pp(librbd::RBD& rbd, librados::IoCtx& io_ctx, size_t num_expected, ...) +{ + int r; + size_t i; + va_list ap; + vector<string> names; + r = rbd.list(io_ctx, names); + if (r == -ENOENT) + r = 0; + EXPECT_TRUE(r >= 0); + cout << "num images is: " << names.size() << std::endl + << "expected: " << num_expected << std::endl; + int num = names.size(); + + for (i = 0; i < names.size(); i++) { + cout << "image: " << names[i] << std::endl; + } + + va_start(ap, num_expected); + for (i = num_expected; i > 0; i--) { + char *expected = va_arg(ap, char *); + cout << "expected = " << expected << std::endl; + vector<string>::iterator listed_name = find(names.begin(), names.end(), string(expected)); + if (listed_name == names.end()) { + ADD_FAILURE() << "Unable to find image " << expected; + va_end(ap); + return -ENOENT; + } + names.erase(listed_name); + } + va_end(ap); + + if (!names.empty()) { + ADD_FAILURE() << "Unexpected images discovered"; + return -EINVAL; + } + return num; +} + +TEST_F(TestLibRBD, TestCreateLsDeletePP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(create_pool(true).c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + std::string name2 = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(1, test_ls_pp(rbd, ioctx, 1, name.c_str())); + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name2.c_str(), size, &order)); + ASSERT_EQ(2, test_ls_pp(rbd, ioctx, 2, name.c_str(), name2.c_str())); + ASSERT_EQ(0, rbd.remove(ioctx, name.c_str())); + ASSERT_EQ(1, test_ls_pp(rbd, ioctx, 1, name2.c_str())); + } + + ioctx.close(); +} + + +static int print_progress_percent(uint64_t offset, uint64_t src_size, + void *data) +{ + float percent = ((float)offset * 100) / src_size; + printf("%3.2f%% done\n", percent); + return 0; +} + +TEST_F(TestLibRBD, TestCopy) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, create_pool(true).c_str(), &ioctx); + + rbd_image_t image; + rbd_image_t image2; + rbd_image_t image3; + int order = 0; + std::string name = get_temp_image_name(); + std::string name2 = get_temp_image_name(); + std::string name3 = get_temp_image_name(); + + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + ASSERT_EQ(1, test_ls(ioctx, 1, name.c_str())); + + size_t sum_key_len = 0; + size_t sum_value_len = 0; + std::string key; + std::string val; + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_set(image, key.c_str(), val.c_str())); + + sum_key_len += (key.size() + 1); + sum_value_len += (val.size() + 1); + } + + char keys[1024]; + char vals[1024]; + size_t keys_len = sizeof(keys); + size_t vals_len = sizeof(vals); + + char value[1024]; + size_t value_len = sizeof(value); + + ASSERT_EQ(0, rbd_copy(image, ioctx, name2.c_str())); + ASSERT_EQ(2, test_ls(ioctx, 2, name.c_str(), name2.c_str())); + ASSERT_EQ(0, rbd_open(ioctx, name2.c_str(), &image2, NULL)); + ASSERT_EQ(0, rbd_metadata_list(image2, "", 70, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, sum_key_len); + ASSERT_EQ(vals_len, sum_value_len); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_get(image2, key.c_str(), value, &value_len)); + ASSERT_STREQ(val.c_str(), value); + + value_len = sizeof(value); + } + + ASSERT_EQ(0, rbd_copy_with_progress(image, ioctx, name3.c_str(), + print_progress_percent, NULL)); + ASSERT_EQ(3, test_ls(ioctx, 3, name.c_str(), name2.c_str(), name3.c_str())); + + keys_len = sizeof(keys); + vals_len = sizeof(vals); + ASSERT_EQ(0, rbd_open(ioctx, name3.c_str(), &image3, NULL)); + ASSERT_EQ(0, rbd_metadata_list(image3, "", 70, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, sum_key_len); + ASSERT_EQ(vals_len, sum_value_len); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_get(image3, key.c_str(), value, &value_len)); + ASSERT_STREQ(val.c_str(), value); + + value_len = sizeof(value); + } + + ASSERT_EQ(0, rbd_close(image)); + ASSERT_EQ(0, rbd_close(image2)); + ASSERT_EQ(0, rbd_close(image3)); + rados_ioctx_destroy(ioctx); +} + +class PrintProgress : public librbd::ProgressContext +{ +public: + int update_progress(uint64_t offset, uint64_t src_size) override + { + float percent = ((float)offset * 100) / src_size; + printf("%3.2f%% done\n", percent); + return 0; + } +}; + +TEST_F(TestLibRBD, TestCopyPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(create_pool(true).c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + librbd::Image image2; + librbd::Image image3; + int order = 0; + std::string name = get_temp_image_name(); + std::string name2 = get_temp_image_name(); + std::string name3 = get_temp_image_name(); + uint64_t size = 2 << 20; + PrintProgress pp; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string key; + std::string val; + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, image.metadata_set(key, val)); + } + + ASSERT_EQ(1, test_ls_pp(rbd, ioctx, 1, name.c_str())); + ASSERT_EQ(0, image.copy(ioctx, name2.c_str())); + ASSERT_EQ(2, test_ls_pp(rbd, ioctx, 2, name.c_str(), name2.c_str())); + ASSERT_EQ(0, rbd.open(ioctx, image2, name2.c_str(), NULL)); + + map<string, bufferlist> pairs; + std::string value; + ASSERT_EQ(0, image2.metadata_list("", 70, &pairs)); + ASSERT_EQ(70U, pairs.size()); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, image2.metadata_get(key.c_str(), &value)); + ASSERT_STREQ(val.c_str(), value.c_str()); + } + + ASSERT_EQ(0, image.copy_with_progress(ioctx, name3.c_str(), pp)); + ASSERT_EQ(3, test_ls_pp(rbd, ioctx, 3, name.c_str(), name2.c_str(), + name3.c_str())); + ASSERT_EQ(0, rbd.open(ioctx, image3, name3.c_str(), NULL)); + + pairs.clear(); + ASSERT_EQ(0, image3.metadata_list("", 70, &pairs)); + ASSERT_EQ(70U, pairs.size()); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, image3.metadata_get(key.c_str(), &value)); + ASSERT_STREQ(val.c_str(), value.c_str()); + } + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestDeepCopy) +{ + REQUIRE_FORMAT_V2(); + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, create_pool(true).c_str(), &ioctx); + BOOST_SCOPE_EXIT_ALL( (&ioctx) ) { + rados_ioctx_destroy(ioctx); + }; + + rbd_image_t image; + rbd_image_t image2; + rbd_image_t image3; + rbd_image_t image4; + rbd_image_t image5; + rbd_image_t image6; + int order = 0; + std::string name = get_temp_image_name(); + std::string name2 = get_temp_image_name(); + std::string name3 = get_temp_image_name(); + std::string name4 = get_temp_image_name(); + std::string name5 = get_temp_image_name(); + std::string name6 = get_temp_image_name(); + + uint64_t size = 2 << 20; + + rbd_image_options_t opts; + rbd_image_options_create(&opts); + BOOST_SCOPE_EXIT_ALL( (&opts) ) { + rbd_image_options_destroy(opts); + }; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + BOOST_SCOPE_EXIT_ALL( (&image) ) { + ASSERT_EQ(0, rbd_close(image)); + }; + ASSERT_EQ(1, test_ls(ioctx, 1, name.c_str())); + + size_t sum_key_len = 0; + size_t sum_value_len = 0; + std::string key; + std::string val; + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_set(image, key.c_str(), val.c_str())); + + sum_key_len += (key.size() + 1); + sum_value_len += (val.size() + 1); + } + + char keys[1024]; + char vals[1024]; + size_t keys_len = sizeof(keys); + size_t vals_len = sizeof(vals); + + char value[1024]; + size_t value_len = sizeof(value); + + ASSERT_EQ(0, rbd_deep_copy(image, ioctx, name2.c_str(), opts)); + ASSERT_EQ(2, test_ls(ioctx, 2, name.c_str(), name2.c_str())); + ASSERT_EQ(0, rbd_open(ioctx, name2.c_str(), &image2, NULL)); + BOOST_SCOPE_EXIT_ALL( (&image2) ) { + ASSERT_EQ(0, rbd_close(image2)); + }; + ASSERT_EQ(0, rbd_metadata_list(image2, "", 70, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, sum_key_len); + ASSERT_EQ(vals_len, sum_value_len); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_get(image2, key.c_str(), value, &value_len)); + ASSERT_STREQ(val.c_str(), value); + + value_len = sizeof(value); + } + + ASSERT_EQ(0, rbd_deep_copy_with_progress(image, ioctx, name3.c_str(), opts, + print_progress_percent, NULL)); + ASSERT_EQ(3, test_ls(ioctx, 3, name.c_str(), name2.c_str(), name3.c_str())); + + keys_len = sizeof(keys); + vals_len = sizeof(vals); + ASSERT_EQ(0, rbd_open(ioctx, name3.c_str(), &image3, NULL)); + BOOST_SCOPE_EXIT_ALL( (&image3) ) { + ASSERT_EQ(0, rbd_close(image3)); + }; + ASSERT_EQ(0, rbd_metadata_list(image3, "", 70, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, sum_key_len); + ASSERT_EQ(vals_len, sum_value_len); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_get(image3, key.c_str(), value, &value_len)); + ASSERT_STREQ(val.c_str(), value); + + value_len = sizeof(value); + } + + ASSERT_EQ(0, rbd_snap_create(image, "deep_snap")); + ASSERT_EQ(0, rbd_close(image)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, "deep_snap")); + ASSERT_EQ(0, rbd_snap_protect(image, "deep_snap")); + ASSERT_EQ(0, rbd_clone3(ioctx, name.c_str(), "deep_snap", ioctx, + name4.c_str(), opts)); + + ASSERT_EQ(4, test_ls(ioctx, 4, name.c_str(), name2.c_str(), name3.c_str(), + name4.c_str())); + ASSERT_EQ(0, rbd_open(ioctx, name4.c_str(), &image4, NULL)); + BOOST_SCOPE_EXIT_ALL( (&image4) ) { + ASSERT_EQ(0, rbd_close(image4)); + }; + ASSERT_EQ(0, rbd_snap_create(image4, "deep_snap")); + + ASSERT_EQ(0, rbd_deep_copy(image4, ioctx, name5.c_str(), opts)); + ASSERT_EQ(5, test_ls(ioctx, 5, name.c_str(), name2.c_str(), name3.c_str(), + name4.c_str(), name5.c_str())); + ASSERT_EQ(0, rbd_open(ioctx, name5.c_str(), &image5, NULL)); + BOOST_SCOPE_EXIT_ALL( (&image5) ) { + ASSERT_EQ(0, rbd_close(image5)); + }; + ASSERT_EQ(0, rbd_metadata_list(image5, "", 70, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, sum_key_len); + ASSERT_EQ(vals_len, sum_value_len); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_get(image5, key.c_str(), value, &value_len)); + ASSERT_STREQ(val.c_str(), value); + + value_len = sizeof(value); + } + + ASSERT_EQ(0, rbd_deep_copy_with_progress(image4, ioctx, name6.c_str(), opts, + print_progress_percent, NULL)); + ASSERT_EQ(6, test_ls(ioctx, 6, name.c_str(), name2.c_str(), name3.c_str(), + name4.c_str(), name5.c_str(), name6.c_str())); + + keys_len = sizeof(keys); + vals_len = sizeof(vals); + ASSERT_EQ(0, rbd_open(ioctx, name6.c_str(), &image6, NULL)); + BOOST_SCOPE_EXIT_ALL( (&image6) ) { + ASSERT_EQ(0, rbd_close(image6)); + }; + ASSERT_EQ(0, rbd_metadata_list(image6, "", 70, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, sum_key_len); + ASSERT_EQ(vals_len, sum_value_len); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_get(image6, key.c_str(), value, &value_len)); + ASSERT_STREQ(val.c_str(), value); + + value_len = sizeof(value); + } +} + +TEST_F(TestLibRBD, TestDeepCopyPP) +{ + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(create_pool(true).c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + librbd::Image image2; + librbd::Image image3; + int order = 0; + std::string name = get_temp_image_name(); + std::string name2 = get_temp_image_name(); + std::string name3 = get_temp_image_name(); + uint64_t size = 2 << 20; + librbd::ImageOptions opts; + PrintProgress pp; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string key; + std::string val; + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, image.metadata_set(key, val)); + } + + ASSERT_EQ(1, test_ls_pp(rbd, ioctx, 1, name.c_str())); + ASSERT_EQ(0, image.deep_copy(ioctx, name2.c_str(), opts)); + ASSERT_EQ(2, test_ls_pp(rbd, ioctx, 2, name.c_str(), name2.c_str())); + ASSERT_EQ(0, rbd.open(ioctx, image2, name2.c_str(), NULL)); + + map<string, bufferlist> pairs; + std::string value; + ASSERT_EQ(0, image2.metadata_list("", 70, &pairs)); + ASSERT_EQ(70U, pairs.size()); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, image2.metadata_get(key.c_str(), &value)); + ASSERT_STREQ(val.c_str(), value.c_str()); + } + + ASSERT_EQ(0, image.deep_copy_with_progress(ioctx, name3.c_str(), opts, pp)); + ASSERT_EQ(3, test_ls_pp(rbd, ioctx, 3, name.c_str(), name2.c_str(), + name3.c_str())); + ASSERT_EQ(0, rbd.open(ioctx, image3, name3.c_str(), NULL)); + + pairs.clear(); + ASSERT_EQ(0, image3.metadata_list("", 70, &pairs)); + ASSERT_EQ(70U, pairs.size()); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, image3.metadata_get(key.c_str(), &value)); + ASSERT_STREQ(val.c_str(), value.c_str()); + } + } + + ioctx.close(); +} + +int test_ls_snaps(rbd_image_t image, int num_expected, ...) +{ + int num_snaps, i, j, max_size = 10; + va_list ap; + rbd_snap_info_t snaps[max_size]; + num_snaps = rbd_snap_list(image, snaps, &max_size); + printf("num snaps is: %d\nexpected: %d\n", num_snaps, num_expected); + + for (i = 0; i < num_snaps; i++) { + printf("snap: %s\n", snaps[i].name); + } + + va_start(ap, num_expected); + for (i = num_expected; i > 0; i--) { + char *expected = va_arg(ap, char *); + uint64_t expected_size = va_arg(ap, uint64_t); + bool found = false; + for (j = 0; j < num_snaps; j++) { + if (snaps[j].name == NULL) + continue; + if (strcmp(snaps[j].name, expected) == 0) { + printf("found %s with size %llu\n", snaps[j].name, (unsigned long long) snaps[j].size); + EXPECT_EQ(expected_size, snaps[j].size); + free((void *) snaps[j].name); + snaps[j].name = NULL; + found = true; + break; + } + } + EXPECT_TRUE(found); + } + va_end(ap); + + for (i = 0; i < num_snaps; i++) { + EXPECT_EQ((const char *)0, snaps[i].name); + } + + return num_snaps; +} + +TEST_F(TestLibRBD, TestCreateLsDeleteSnap) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + uint64_t size2 = 4 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_snap_create(image, "snap1")); + ASSERT_EQ(1, test_ls_snaps(image, 1, "snap1", size)); + ASSERT_EQ(0, rbd_resize(image, size2)); + ASSERT_EQ(0, rbd_snap_create(image, "snap2")); + ASSERT_EQ(2, test_ls_snaps(image, 2, "snap1", size, "snap2", size2)); + ASSERT_EQ(0, rbd_snap_remove(image, "snap1")); + ASSERT_EQ(1, test_ls_snaps(image, 1, "snap2", size2)); + ASSERT_EQ(0, rbd_snap_remove(image, "snap2")); + ASSERT_EQ(0, test_ls_snaps(image, 0)); + + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +int test_get_snapshot_timestamp(rbd_image_t image, uint64_t snap_id) +{ + struct timespec timestamp; + EXPECT_EQ(0, rbd_snap_get_timestamp(image, snap_id, ×tamp)); + EXPECT_LT(0, timestamp.tv_sec); + return 0; +} + +TEST_F(TestLibRBD, TestGetSnapShotTimeStamp) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int num_snaps, max_size = 10; + rbd_snap_info_t snaps[max_size]; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_snap_create(image, "snap1")); + num_snaps = rbd_snap_list(image, snaps, &max_size); + ASSERT_EQ(1, num_snaps); + ASSERT_EQ(0, test_get_snapshot_timestamp(image, snaps[0].id)); + free((void *)snaps[0].name); + + ASSERT_EQ(0, rbd_snap_create(image, "snap2")); + num_snaps = rbd_snap_list(image, snaps, &max_size); + ASSERT_EQ(2, num_snaps); + ASSERT_EQ(0, test_get_snapshot_timestamp(image, snaps[0].id)); + ASSERT_EQ(0, test_get_snapshot_timestamp(image, snaps[1].id)); + free((void *)snaps[0].name); + free((void *)snaps[1].name); + + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + + +int test_ls_snaps(librbd::Image& image, size_t num_expected, ...) +{ + int r; + size_t i, j; + va_list ap; + vector<librbd::snap_info_t> snaps; + r = image.snap_list(snaps); + EXPECT_TRUE(r >= 0); + cout << "num snaps is: " << snaps.size() << std::endl + << "expected: " << num_expected << std::endl; + + for (i = 0; i < snaps.size(); i++) { + cout << "snap: " << snaps[i].name << std::endl; + } + + va_start(ap, num_expected); + for (i = num_expected; i > 0; i--) { + char *expected = va_arg(ap, char *); + uint64_t expected_size = va_arg(ap, uint64_t); + int found = 0; + for (j = 0; j < snaps.size(); j++) { + if (snaps[j].name == "") + continue; + if (strcmp(snaps[j].name.c_str(), expected) == 0) { + cout << "found " << snaps[j].name << " with size " << snaps[j].size + << std::endl; + EXPECT_EQ(expected_size, snaps[j].size); + snaps[j].name = ""; + found = 1; + break; + } + } + EXPECT_TRUE(found); + } + va_end(ap); + + for (i = 0; i < snaps.size(); i++) { + EXPECT_EQ("", snaps[i].name); + } + + return snaps.size(); +} + +TEST_F(TestLibRBD, TestCreateLsDeleteSnapPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + uint64_t size2 = 4 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + bool exists; + ASSERT_EQ(0, image.snap_exists2("snap1", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ(0, image.snap_exists2("snap1", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(1, test_ls_snaps(image, 1, "snap1", size)); + ASSERT_EQ(0, image.resize(size2)); + ASSERT_EQ(0, image.snap_exists2("snap2", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, image.snap_create("snap2")); + ASSERT_EQ(0, image.snap_exists2("snap2", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(2, test_ls_snaps(image, 2, "snap1", size, "snap2", size2)); + ASSERT_EQ(0, image.snap_remove("snap1")); + ASSERT_EQ(0, image.snap_exists2("snap1", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(1, test_ls_snaps(image, 1, "snap2", size2)); + ASSERT_EQ(0, image.snap_remove("snap2")); + ASSERT_EQ(0, image.snap_exists2("snap2", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, test_ls_snaps(image, 0)); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestCreateLsRenameSnapPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + uint64_t size2 = 4 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + bool exists; + ASSERT_EQ(0, image.snap_exists2("snap1", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ(0, image.snap_exists2("snap1", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(1, test_ls_snaps(image, 1, "snap1", size)); + ASSERT_EQ(0, image.resize(size2)); + ASSERT_EQ(0, image.snap_exists2("snap2", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, image.snap_create("snap2")); + ASSERT_EQ(0, image.snap_exists2("snap2", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(2, test_ls_snaps(image, 2, "snap1", size, "snap2", size2)); + ASSERT_EQ(0, image.snap_rename("snap1","snap1-rename")); + ASSERT_EQ(2, test_ls_snaps(image, 2, "snap1-rename", size, "snap2", size2)); + ASSERT_EQ(0, image.snap_exists2("snap1", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, image.snap_exists2("snap1-rename", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(0, image.snap_remove("snap1-rename")); + ASSERT_EQ(0, image.snap_rename("snap2","snap2-rename")); + ASSERT_EQ(1, test_ls_snaps(image, 1, "snap2-rename", size2)); + ASSERT_EQ(0, image.snap_exists2("snap2", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, image.snap_exists2("snap2-rename", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(0, image.snap_remove("snap2-rename")); + ASSERT_EQ(0, test_ls_snaps(image, 0)); + } + + ioctx.close(); +} + +void simple_write_cb(rbd_completion_t cb, void *arg) +{ + printf("write completion cb called!\n"); +} + +void simple_read_cb(rbd_completion_t cb, void *arg) +{ + printf("read completion cb called!\n"); +} + +void aio_write_test_data_and_poll(rbd_image_t image, int fd, const char *test_data, + uint64_t off, size_t len, uint32_t iohint, bool *passed) +{ + rbd_completion_t comp; + uint64_t data = 0x123; + rbd_aio_create_completion((void*)&data, (rbd_callback_t) simple_write_cb, &comp); + printf("created completion\n"); + printf("started write\n"); + if (iohint) + rbd_aio_write2(image, off, len, test_data, comp, iohint); + else + rbd_aio_write(image, off, len, test_data, comp); + + struct pollfd pfd; + pfd.fd = fd; + pfd.events = POLLIN; + + ASSERT_EQ(1, poll(&pfd, 1, -1)); + ASSERT_TRUE(pfd.revents & POLLIN); + + rbd_completion_t comps[1]; + ASSERT_EQ(1, rbd_poll_io_events(image, comps, 1)); + uint64_t count; + ASSERT_EQ(static_cast<ssize_t>(sizeof(count)), + read(fd, &count, sizeof(count))); + int r = rbd_aio_get_return_value(comps[0]); + ASSERT_TRUE(rbd_aio_is_complete(comps[0])); + ASSERT_TRUE(*(uint64_t*)rbd_aio_get_arg(comps[0]) == data); + printf("return value is: %d\n", r); + ASSERT_EQ(0, r); + printf("finished write\n"); + rbd_aio_release(comps[0]); + *passed = true; +} + +void aio_write_test_data(rbd_image_t image, const char *test_data, uint64_t off, size_t len, uint32_t iohint, bool *passed) +{ + rbd_completion_t comp; + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_write_cb, &comp); + printf("created completion\n"); + if (iohint) + rbd_aio_write2(image, off, len, test_data, comp, iohint); + else + rbd_aio_write(image, off, len, test_data, comp); + printf("started write\n"); + rbd_aio_wait_for_complete(comp); + int r = rbd_aio_get_return_value(comp); + printf("return value is: %d\n", r); + ASSERT_EQ(0, r); + printf("finished write\n"); + rbd_aio_release(comp); + *passed = true; +} + +void write_test_data(rbd_image_t image, const char *test_data, uint64_t off, size_t len, uint32_t iohint, bool *passed) +{ + ssize_t written; + if (iohint) + written = rbd_write2(image, off, len, test_data, iohint); + else + written = rbd_write(image, off, len, test_data); + printf("wrote: %d\n", (int) written); + ASSERT_EQ(len, static_cast<size_t>(written)); + *passed = true; +} + +void aio_discard_test_data(rbd_image_t image, uint64_t off, uint64_t len, bool *passed) +{ + rbd_completion_t comp; + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_write_cb, &comp); + rbd_aio_discard(image, off, len, comp); + rbd_aio_wait_for_complete(comp); + int r = rbd_aio_get_return_value(comp); + ASSERT_EQ(0, r); + printf("aio discard: %d~%d = %d\n", (int)off, (int)len, (int)r); + rbd_aio_release(comp); + *passed = true; +} + +void discard_test_data(rbd_image_t image, uint64_t off, size_t len, bool *passed) +{ + ssize_t written; + written = rbd_discard(image, off, len); + printf("discard: %d~%d = %d\n", (int)off, (int)len, (int)written); + ASSERT_EQ(len, static_cast<size_t>(written)); + *passed = true; +} + +void aio_read_test_data_and_poll(rbd_image_t image, int fd, const char *expected, + uint64_t off, size_t len, uint32_t iohint, bool *passed) +{ + rbd_completion_t comp; + char *result = (char *)malloc(len + 1); + + ASSERT_NE(static_cast<char *>(NULL), result); + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp); + printf("created completion\n"); + printf("started read\n"); + if (iohint) + rbd_aio_read2(image, off, len, result, comp, iohint); + else + rbd_aio_read(image, off, len, result, comp); + + struct pollfd pfd; + pfd.fd = fd; + pfd.events = POLLIN; + + ASSERT_EQ(1, poll(&pfd, 1, -1)); + ASSERT_TRUE(pfd.revents & POLLIN); + + rbd_completion_t comps[1]; + ASSERT_EQ(1, rbd_poll_io_events(image, comps, 1)); + uint64_t count; + ASSERT_EQ(static_cast<ssize_t>(sizeof(count)), + read(fd, &count, sizeof(count))); + + int r = rbd_aio_get_return_value(comps[0]); + ASSERT_TRUE(rbd_aio_is_complete(comps[0])); + printf("return value is: %d\n", r); + ASSERT_EQ(len, static_cast<size_t>(r)); + rbd_aio_release(comps[0]); + if (memcmp(result, expected, len)) { + printf("read: %s\nexpected: %s\n", result, expected); + ASSERT_EQ(0, memcmp(result, expected, len)); + } + free(result); + *passed = true; +} + +void aio_read_test_data(rbd_image_t image, const char *expected, uint64_t off, size_t len, uint32_t iohint, bool *passed) +{ + rbd_completion_t comp; + char *result = (char *)malloc(len + 1); + + ASSERT_NE(static_cast<char *>(NULL), result); + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp); + printf("created completion\n"); + if (iohint) + rbd_aio_read2(image, off, len, result, comp, iohint); + else + rbd_aio_read(image, off, len, result, comp); + printf("started read\n"); + rbd_aio_wait_for_complete(comp); + int r = rbd_aio_get_return_value(comp); + printf("return value is: %d\n", r); + ASSERT_EQ(len, static_cast<size_t>(r)); + rbd_aio_release(comp); + if (memcmp(result, expected, len)) { + printf("read: %s\nexpected: %s\n", result, expected); + ASSERT_EQ(0, memcmp(result, expected, len)); + } + free(result); + *passed = true; +} + +void read_test_data(rbd_image_t image, const char *expected, uint64_t off, size_t len, uint32_t iohint, bool *passed) +{ + ssize_t read; + char *result = (char *)malloc(len + 1); + + ASSERT_NE(static_cast<char *>(NULL), result); + if (iohint) + read = rbd_read2(image, off, len, result, iohint); + else + read = rbd_read(image, off, len, result); + printf("read: %d\n", (int) read); + ASSERT_EQ(len, static_cast<size_t>(read)); + result[len] = '\0'; + if (memcmp(result, expected, len)) { + printf("read: %s\nexpected: %s\n", result, expected); + ASSERT_EQ(0, memcmp(result, expected, len)); + } + free(result); + *passed = true; +} + +void aio_writesame_test_data(rbd_image_t image, const char *test_data, uint64_t off, uint64_t len, + uint64_t data_len, uint32_t iohint, bool *passed) +{ + rbd_completion_t comp; + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_write_cb, &comp); + printf("created completion\n"); + int r; + r = rbd_aio_writesame(image, off, len, test_data, data_len, comp, iohint); + printf("started writesame\n"); + if (len % data_len) { + ASSERT_EQ(-EINVAL, r); + printf("expected fail, finished writesame\n"); + rbd_aio_release(comp); + *passed = true; + return; + } + + rbd_aio_wait_for_complete(comp); + r = rbd_aio_get_return_value(comp); + printf("return value is: %d\n", r); + ASSERT_EQ(0, r); + printf("finished writesame\n"); + rbd_aio_release(comp); + + //verify data + printf("to verify the data\n"); + ssize_t read; + char *result = (char *)malloc(data_len+ 1); + ASSERT_NE(static_cast<char *>(NULL), result); + uint64_t left = len; + while (left > 0) { + read = rbd_read(image, off, data_len, result); + ASSERT_EQ(data_len, static_cast<size_t>(read)); + result[data_len] = '\0'; + if (memcmp(result, test_data, data_len)) { + printf("read: %d ~ %d\n", (int) off, (int) read); + printf("read: %s\nexpected: %s\n", result, test_data); + ASSERT_EQ(0, memcmp(result, test_data, data_len)); + } + off += data_len; + left -= data_len; + } + ASSERT_EQ(0U, left); + free(result); + printf("verified\n"); + + *passed = true; +} + +void writesame_test_data(rbd_image_t image, const char *test_data, uint64_t off, uint64_t len, + uint64_t data_len, uint32_t iohint, bool *passed) +{ + ssize_t written; + written = rbd_writesame(image, off, len, test_data, data_len, iohint); + if (len % data_len) { + ASSERT_EQ(-EINVAL, written); + printf("expected fail, finished writesame\n"); + *passed = true; + return; + } + ASSERT_EQ(len, static_cast<size_t>(written)); + printf("wrote: %d\n", (int) written); + + //verify data + printf("to verify the data\n"); + ssize_t read; + char *result = (char *)malloc(data_len+ 1); + ASSERT_NE(static_cast<char *>(NULL), result); + uint64_t left = len; + while (left > 0) { + read = rbd_read(image, off, data_len, result); + ASSERT_EQ(data_len, static_cast<size_t>(read)); + result[data_len] = '\0'; + if (memcmp(result, test_data, data_len)) { + printf("read: %d ~ %d\n", (int) off, (int) read); + printf("read: %s\nexpected: %s\n", result, test_data); + ASSERT_EQ(0, memcmp(result, test_data, data_len)); + } + off += data_len; + left -= data_len; + } + ASSERT_EQ(0U, left); + free(result); + printf("verified\n"); + + *passed = true; +} + +void aio_compare_and_write_test_data(rbd_image_t image, const char *cmp_data, + const char *test_data, uint64_t off, + size_t len, uint32_t iohint, bool *passed) +{ + rbd_completion_t comp; + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_write_cb, &comp); + printf("created completion\n"); + + uint64_t mismatch_offset; + rbd_aio_compare_and_write(image, off, len, cmp_data, test_data, comp, &mismatch_offset, iohint); + printf("started aio compare and write\n"); + rbd_aio_wait_for_complete(comp); + int r = rbd_aio_get_return_value(comp); + printf("return value is: %d\n", r); + ASSERT_EQ(0, r); + printf("finished aio compare and write\n"); + rbd_aio_release(comp); + *passed = true; +} + +void compare_and_write_test_data(rbd_image_t image, const char *cmp_data, + const char *test_data, uint64_t off, size_t len, + uint64_t *mismatch_off, uint32_t iohint, bool *passed) +{ + printf("start compare and write\n"); + ssize_t written; + written = rbd_compare_and_write(image, off, len, cmp_data, test_data, mismatch_off, iohint); + printf("compare and wrote: %d\n", (int) written); + ASSERT_EQ(len, static_cast<size_t>(written)); + *passed = true; +} + + +TEST_F(TestLibRBD, TestIO) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + bool skip_discard = is_skip_partial_discard_enabled(); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + char test_data[TEST_IO_SIZE + 1]; + char zero_data[TEST_IO_SIZE + 1]; + char mismatch_data[TEST_IO_SIZE + 1]; + int i; + uint64_t mismatch_offset; + + for (i = 0; i < TEST_IO_SIZE; ++i) { + test_data[i] = (char) (rand() % (126 - 33) + 33); + } + test_data[TEST_IO_SIZE] = '\0'; + memset(zero_data, 0, sizeof(zero_data)); + memset(mismatch_data, 9, sizeof(mismatch_data)); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(write_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_write_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(compare_and_write_test_data, image, test_data, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, &mismatch_offset, 0); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_compare_and_write_test_data, image, test_data, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(read_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_read_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + // discard 2nd, 4th sections. + ASSERT_PASSED(discard_test_data, image, TEST_IO_SIZE, TEST_IO_SIZE); + ASSERT_PASSED(aio_discard_test_data, image, TEST_IO_SIZE*3, TEST_IO_SIZE); + + ASSERT_PASSED(read_test_data, image, test_data, 0, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, skip_discard ? test_data : zero_data, + TEST_IO_SIZE, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, test_data, TEST_IO_SIZE*2, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, skip_discard ? test_data : zero_data, + TEST_IO_SIZE*3, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, test_data, TEST_IO_SIZE*4, TEST_IO_SIZE, 0); + + for (i = 0; i < 15; ++i) { + if (i % 3 == 2) { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, 0); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, 0); + } else if (i % 3 == 1) { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + } else { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + } + } + for (i = 0; i < 15; ++i) { + if (i % 3 == 2) { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, 0); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, 0); + } else if (i % 3 == 1) { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + } else { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + } + } + + rbd_image_info_t info; + rbd_completion_t comp; + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + // can't read or write starting past end + ASSERT_EQ(-EINVAL, rbd_write(image, info.size, 1, test_data)); + ASSERT_EQ(-EINVAL, rbd_read(image, info.size, 1, test_data)); + // reading through end returns amount up to end + ASSERT_EQ(10, rbd_read(image, info.size - 10, 100, test_data)); + // writing through end returns amount up to end + ASSERT_EQ(10, rbd_write(image, info.size - 10, 100, test_data)); + + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp); + ASSERT_EQ(0, rbd_aio_write(image, info.size, 1, test_data, comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(comp)); + ASSERT_EQ(-EINVAL, rbd_aio_get_return_value(comp)); + rbd_aio_release(comp); + + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp); + ASSERT_EQ(0, rbd_aio_read(image, info.size, 1, test_data, comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(comp)); + ASSERT_EQ(-EINVAL, rbd_aio_get_return_value(comp)); + rbd_aio_release(comp); + + ASSERT_PASSED(write_test_data, image, zero_data, 0, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + ASSERT_EQ(-EILSEQ, rbd_compare_and_write(image, 0, TEST_IO_SIZE, mismatch_data, mismatch_data, &mismatch_offset, 0)); + ASSERT_EQ(0U, mismatch_offset); + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp); + ASSERT_EQ(0, rbd_aio_compare_and_write(image, 0, TEST_IO_SIZE, mismatch_data, mismatch_data, comp, &mismatch_offset, 0)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(comp)); + ASSERT_EQ(0U, mismatch_offset); + rbd_aio_release(comp); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, TestIOWithIOHint) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + bool skip_discard = is_skip_partial_discard_enabled(); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + char test_data[TEST_IO_SIZE + 1]; + char zero_data[TEST_IO_SIZE + 1]; + char mismatch_data[TEST_IO_SIZE + 1]; + int i; + uint64_t mismatch_offset; + + for (i = 0; i < TEST_IO_SIZE; ++i) { + test_data[i] = (char) (rand() % (126 - 33) + 33); + } + test_data[TEST_IO_SIZE] = '\0'; + memset(zero_data, 0, sizeof(zero_data)); + memset(mismatch_data, 9, sizeof(mismatch_data)); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(write_test_data, image, test_data, TEST_IO_SIZE * i, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_write_test_data, image, test_data, TEST_IO_SIZE * i, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(compare_and_write_test_data, image, test_data, test_data, + TEST_IO_SIZE * i, TEST_IO_SIZE, &mismatch_offset, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_compare_and_write_test_data, image, test_data, test_data, + TEST_IO_SIZE * i, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(read_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, + LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_read_test_data, image, test_data, TEST_IO_SIZE * i, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL|LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + + // discard 2nd, 4th sections. + ASSERT_PASSED(discard_test_data, image, TEST_IO_SIZE, TEST_IO_SIZE); + ASSERT_PASSED(aio_discard_test_data, image, TEST_IO_SIZE*3, TEST_IO_SIZE); + + ASSERT_PASSED(read_test_data, image, test_data, 0, TEST_IO_SIZE, + LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL); + ASSERT_PASSED(read_test_data, image, skip_discard ? test_data : zero_data, + TEST_IO_SIZE, TEST_IO_SIZE, + LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL); + ASSERT_PASSED(read_test_data, image, test_data, TEST_IO_SIZE*2, TEST_IO_SIZE, + LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL); + ASSERT_PASSED(read_test_data, image, skip_discard ? test_data : zero_data, + TEST_IO_SIZE*3, TEST_IO_SIZE, + LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL); + ASSERT_PASSED(read_test_data, image, test_data, TEST_IO_SIZE*4, TEST_IO_SIZE, 0); + + for (i = 0; i < 15; ++i) { + if (i % 3 == 2) { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + } else if (i % 3 == 1) { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + } else { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + } + } + for (i = 0; i < 15; ++i) { + if (i % 3 == 2) { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + } else if (i % 3 == 1) { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + } else { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + } + } + + rbd_image_info_t info; + rbd_completion_t comp; + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + // can't read or write starting past end + ASSERT_EQ(-EINVAL, rbd_write(image, info.size, 1, test_data)); + ASSERT_EQ(-EINVAL, rbd_read(image, info.size, 1, test_data)); + // reading through end returns amount up to end + ASSERT_EQ(10, rbd_read2(image, info.size - 10, 100, test_data, + LIBRADOS_OP_FLAG_FADVISE_NOCACHE)); + // writing through end returns amount up to end + ASSERT_EQ(10, rbd_write2(image, info.size - 10, 100, test_data, + LIBRADOS_OP_FLAG_FADVISE_DONTNEED)); + + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp); + ASSERT_EQ(0, rbd_aio_read2(image, info.size, 1, test_data, comp, + LIBRADOS_OP_FLAG_FADVISE_DONTNEED)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(comp)); + ASSERT_EQ(-EINVAL, rbd_aio_get_return_value(comp)); + rbd_aio_release(comp); + + ASSERT_PASSED(write_test_data, image, zero_data, 0, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + ASSERT_EQ(-EILSEQ, rbd_compare_and_write(image, 0, TEST_IO_SIZE, mismatch_data, mismatch_data, + &mismatch_offset, LIBRADOS_OP_FLAG_FADVISE_DONTNEED)); + ASSERT_EQ(0U, mismatch_offset); + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp); + ASSERT_EQ(0, rbd_aio_compare_and_write(image, 0, TEST_IO_SIZE, mismatch_data, mismatch_data, + comp, &mismatch_offset, LIBRADOS_OP_FLAG_FADVISE_DONTNEED)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(comp)); + ASSERT_EQ(0U, mismatch_offset); + rbd_aio_release(comp); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, TestDataPoolIO) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + std::string data_pool_name = create_pool(true); + + bool skip_discard = is_skip_partial_discard_enabled(); + + rbd_image_t image; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + bool old_format; + uint64_t features; + ASSERT_EQ(0, get_features(&old_format, &features)); + ASSERT_FALSE(old_format); + + rbd_image_options_t image_options; + rbd_image_options_create(&image_options); + BOOST_SCOPE_EXIT( (&image_options) ) { + rbd_image_options_destroy(image_options); + } BOOST_SCOPE_EXIT_END; + + ASSERT_EQ(0, rbd_image_options_set_uint64(image_options, + RBD_IMAGE_OPTION_FEATURES, + features)); + ASSERT_EQ(0, rbd_image_options_set_string(image_options, + RBD_IMAGE_OPTION_DATA_POOL, + data_pool_name.c_str())); + + ASSERT_EQ(0, rbd_create4(ioctx, name.c_str(), size, image_options)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + ASSERT_NE(-1, rbd_get_data_pool_id(image)); + + char test_data[TEST_IO_SIZE + 1]; + char zero_data[TEST_IO_SIZE + 1]; + int i; + + for (i = 0; i < TEST_IO_SIZE; ++i) { + test_data[i] = (char) (rand() % (126 - 33) + 33); + } + test_data[TEST_IO_SIZE] = '\0'; + memset(zero_data, 0, sizeof(zero_data)); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(write_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_write_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(read_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_read_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + // discard 2nd, 4th sections. + ASSERT_PASSED(discard_test_data, image, TEST_IO_SIZE, TEST_IO_SIZE); + ASSERT_PASSED(aio_discard_test_data, image, TEST_IO_SIZE*3, TEST_IO_SIZE); + + ASSERT_PASSED(read_test_data, image, test_data, 0, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, skip_discard ? test_data : zero_data, + TEST_IO_SIZE, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, test_data, TEST_IO_SIZE*2, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, skip_discard ? test_data : zero_data, + TEST_IO_SIZE*3, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, test_data, TEST_IO_SIZE*4, TEST_IO_SIZE, 0); + + rbd_image_info_t info; + rbd_completion_t comp; + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + // can't read or write starting past end + ASSERT_EQ(-EINVAL, rbd_write(image, info.size, 1, test_data)); + ASSERT_EQ(-EINVAL, rbd_read(image, info.size, 1, test_data)); + // reading through end returns amount up to end + ASSERT_EQ(10, rbd_read(image, info.size - 10, 100, test_data)); + // writing through end returns amount up to end + ASSERT_EQ(10, rbd_write(image, info.size - 10, 100, test_data)); + + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp); + ASSERT_EQ(0, rbd_aio_write(image, info.size, 1, test_data, comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(comp)); + ASSERT_EQ(-EINVAL, rbd_aio_get_return_value(comp)); + rbd_aio_release(comp); + + rbd_aio_create_completion(NULL, (rbd_callback_t) simple_read_cb, &comp); + ASSERT_EQ(0, rbd_aio_read(image, info.size, 1, test_data, comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(comp)); + ASSERT_EQ(-EINVAL, rbd_aio_get_return_value(comp)); + rbd_aio_release(comp); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, TestScatterGatherIO) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + std::string write_buffer("This is a test"); + struct iovec bad_iovs[] = { + {.iov_base = NULL, .iov_len = static_cast<size_t>(-1)} + }; + struct iovec write_iovs[] = { + {.iov_base = &write_buffer[0], .iov_len = 5}, + {.iov_base = &write_buffer[5], .iov_len = 3}, + {.iov_base = &write_buffer[8], .iov_len = 2}, + {.iov_base = &write_buffer[10], .iov_len = 4} + }; + + rbd_completion_t comp; + rbd_aio_create_completion(NULL, NULL, &comp); + ASSERT_EQ(-EINVAL, rbd_aio_writev(image, write_iovs, 0, 0, comp)); + ASSERT_EQ(-EINVAL, rbd_aio_writev(image, bad_iovs, 1, 0, comp)); + ASSERT_EQ(0, rbd_aio_writev(image, write_iovs, + sizeof(write_iovs) / sizeof(struct iovec), + 1<<order, comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(comp)); + ASSERT_EQ(0, rbd_aio_get_return_value(comp)); + rbd_aio_release(comp); + + std::string read_buffer(write_buffer.size(), '1'); + struct iovec read_iovs[] = { + {.iov_base = &read_buffer[0], .iov_len = 4}, + {.iov_base = &read_buffer[8], .iov_len = 4}, + {.iov_base = &read_buffer[12], .iov_len = 2} + }; + + rbd_aio_create_completion(NULL, NULL, &comp); + ASSERT_EQ(-EINVAL, rbd_aio_readv(image, read_iovs, 0, 0, comp)); + ASSERT_EQ(-EINVAL, rbd_aio_readv(image, bad_iovs, 1, 0, comp)); + ASSERT_EQ(0, rbd_aio_readv(image, read_iovs, + sizeof(read_iovs) / sizeof(struct iovec), + 1<<order, comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(comp)); + ASSERT_EQ(10, rbd_aio_get_return_value(comp)); + rbd_aio_release(comp); + ASSERT_EQ("This1111 is a ", read_buffer); + + std::string linear_buffer(write_buffer.size(), '1'); + struct iovec linear_iovs[] = { + {.iov_base = &linear_buffer[4], .iov_len = 4} + }; + rbd_aio_create_completion(NULL, NULL, &comp); + ASSERT_EQ(0, rbd_aio_readv(image, linear_iovs, + sizeof(linear_iovs) / sizeof(struct iovec), + 1<<order, comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(comp)); + ASSERT_EQ(4, rbd_aio_get_return_value(comp)); + rbd_aio_release(comp); + ASSERT_EQ("1111This111111", linear_buffer); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, TestEmptyDiscard) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_PASSED(aio_discard_test_data, image, 0, 1*1024*1024); + ASSERT_PASSED(aio_discard_test_data, image, 0, 4*1024*1024); + ASSERT_PASSED(aio_discard_test_data, image, 3*1024*1024, 1*1024*1024); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, TestFUA) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image_write; + rbd_image_t image_read; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image_write, NULL)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image_read, NULL)); + + // enable writeback cache + rbd_flush(image_write); + + char test_data[TEST_IO_SIZE + 1]; + int i; + + for (i = 0; i < TEST_IO_SIZE; ++i) { + test_data[i] = (char) (rand() % (126 - 33) + 33); + } + test_data[TEST_IO_SIZE] = '\0'; + for (i = 0; i < 5; ++i) + ASSERT_PASSED(write_test_data, image_write, test_data, + TEST_IO_SIZE * i, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_FUA); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(read_test_data, image_read, test_data, + TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_write_test_data, image_write, test_data, + TEST_IO_SIZE * i, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_FUA); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_read_test_data, image_read, test_data, + TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + ASSERT_PASSED(validate_object_map, image_write); + ASSERT_PASSED(validate_object_map, image_read); + ASSERT_EQ(0, rbd_close(image_write)); + ASSERT_EQ(0, rbd_close(image_read)); + ASSERT_EQ(0, rbd_remove(ioctx, name.c_str())); + rados_ioctx_destroy(ioctx); +} + +void simple_write_cb_pp(librbd::completion_t cb, void *arg) +{ + cout << "write completion cb called!" << std::endl; +} + +void simple_read_cb_pp(librbd::completion_t cb, void *arg) +{ + cout << "read completion cb called!" << std::endl; +} + +void aio_write_test_data(librbd::Image& image, const char *test_data, + off_t off, uint32_t iohint, bool *passed) +{ + ceph::bufferlist bl; + bl.append(test_data, strlen(test_data)); + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, (librbd::callback_t) simple_write_cb_pp); + printf("created completion\n"); + if (iohint) + image.aio_write2(off, strlen(test_data), bl, comp, iohint); + else + image.aio_write(off, strlen(test_data), bl, comp); + printf("started write\n"); + comp->wait_for_complete(); + int r = comp->get_return_value(); + printf("return value is: %d\n", r); + ASSERT_EQ(0, r); + printf("finished write\n"); + comp->release(); + *passed = true; +} + +void aio_discard_test_data(librbd::Image& image, off_t off, size_t len, bool *passed) +{ + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, (librbd::callback_t) simple_write_cb_pp); + image.aio_discard(off, len, comp); + comp->wait_for_complete(); + int r = comp->get_return_value(); + ASSERT_EQ(0, r); + comp->release(); + *passed = true; +} + +void write_test_data(librbd::Image& image, const char *test_data, off_t off, uint32_t iohint, bool *passed) +{ + size_t written; + size_t len = strlen(test_data); + ceph::bufferlist bl; + bl.append(test_data, len); + if (iohint) + written = image.write2(off, len, bl, iohint); + else + written = image.write(off, len, bl); + printf("wrote: %u\n", (unsigned int) written); + ASSERT_EQ(bl.length(), written); + *passed = true; +} + +void discard_test_data(librbd::Image& image, off_t off, size_t len, bool *passed) +{ + size_t written; + written = image.discard(off, len); + printf("discard: %u~%u\n", (unsigned)off, (unsigned)len); + ASSERT_EQ(len, written); + *passed = true; +} + +void aio_read_test_data(librbd::Image& image, const char *expected, off_t off, size_t expected_len, uint32_t iohint, bool *passed) +{ + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, (librbd::callback_t) simple_read_cb_pp); + ceph::bufferlist bl; + printf("created completion\n"); + if (iohint) + image.aio_read2(off, expected_len, bl, comp, iohint); + else + image.aio_read(off, expected_len, bl, comp); + printf("started read\n"); + comp->wait_for_complete(); + int r = comp->get_return_value(); + printf("return value is: %d\n", r); + ASSERT_EQ(TEST_IO_SIZE, r); + ASSERT_EQ(0, memcmp(expected, bl.c_str(), TEST_IO_SIZE)); + printf("finished read\n"); + comp->release(); + *passed = true; +} + +void read_test_data(librbd::Image& image, const char *expected, off_t off, size_t expected_len, uint32_t iohint, bool *passed) +{ + int read; + size_t len = expected_len; + ceph::bufferlist bl; + if (iohint) + read = image.read2(off, len, bl, iohint); + else + read = image.read(off, len, bl); + ASSERT_TRUE(read >= 0); + std::string bl_str(bl.c_str(), read); + + printf("read: %u\n", (unsigned int) read); + int result = memcmp(bl_str.c_str(), expected, expected_len); + if (result != 0) { + printf("read: %s\nexpected: %s\n", bl_str.c_str(), expected); + ASSERT_EQ(0, result); + } + *passed = true; +} + +void aio_writesame_test_data(librbd::Image& image, const char *test_data, off_t off, + size_t len, size_t data_len, uint32_t iohint, bool *passed) +{ + ceph::bufferlist bl; + bl.append(test_data, data_len); + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, (librbd::callback_t) simple_write_cb_pp); + printf("created completion\n"); + int r; + r = image.aio_writesame(off, len, bl, comp, iohint); + printf("started writesame\n"); + if (len % data_len) { + ASSERT_EQ(-EINVAL, r); + printf("expected fail, finished writesame\n"); + comp->release(); + *passed = true; + return; + } + + comp->wait_for_complete(); + r = comp->get_return_value(); + printf("return value is: %d\n", r); + ASSERT_EQ(0, r); + printf("finished writesame\n"); + comp->release(); + + //verify data + printf("to verify the data\n"); + int read; + uint64_t left = len; + while (left > 0) { + ceph::bufferlist bl; + read = image.read(off, data_len, bl); + ASSERT_EQ(data_len, static_cast<size_t>(read)); + std::string bl_str(bl.c_str(), read); + int result = memcmp(bl_str.c_str(), test_data, data_len); + if (result !=0 ) { + printf("read: %u ~ %u\n", (unsigned int) off, (unsigned int) read); + printf("read: %s\nexpected: %s\n", bl_str.c_str(), test_data); + ASSERT_EQ(0, result); + } + off += data_len; + left -= data_len; + } + ASSERT_EQ(0U, left); + printf("verified\n"); + + *passed = true; +} + +void writesame_test_data(librbd::Image& image, const char *test_data, off_t off, + ssize_t len, size_t data_len, uint32_t iohint, + bool *passed) +{ + ssize_t written; + ceph::bufferlist bl; + bl.append(test_data, data_len); + written = image.writesame(off, len, bl, iohint); + if (len % data_len) { + ASSERT_EQ(-EINVAL, written); + printf("expected fail, finished writesame\n"); + *passed = true; + return; + } + ASSERT_EQ(len, written); + printf("wrote: %u\n", (unsigned int) written); + *passed = true; + + //verify data + printf("to verify the data\n"); + int read; + uint64_t left = len; + while (left > 0) { + ceph::bufferlist bl; + read = image.read(off, data_len, bl); + ASSERT_EQ(data_len, static_cast<size_t>(read)); + std::string bl_str(bl.c_str(), read); + int result = memcmp(bl_str.c_str(), test_data, data_len); + if (result !=0 ) { + printf("read: %u ~ %u\n", (unsigned int) off, (unsigned int) read); + printf("read: %s\nexpected: %s\n", bl_str.c_str(), test_data); + ASSERT_EQ(0, result); + } + off += data_len; + left -= data_len; + } + ASSERT_EQ(0U, left); + printf("verified\n"); + + *passed = true; +} + +void aio_compare_and_write_test_data(librbd::Image& image, const char *cmp_data, + const char *test_data, off_t off, ssize_t len, + uint32_t iohint, bool *passed) +{ + ceph::bufferlist cmp_bl; + cmp_bl.append(cmp_data, strlen(cmp_data)); + ceph::bufferlist test_bl; + test_bl.append(test_data, strlen(test_data)); + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, (librbd::callback_t) simple_write_cb_pp); + printf("created completion\n"); + + uint64_t mismatch_offset; + image.aio_compare_and_write(off, len, cmp_bl, test_bl, comp, &mismatch_offset, iohint); + printf("started aio compare and write\n"); + comp->wait_for_complete(); + int r = comp->get_return_value(); + printf("return value is: %d\n", r); + ASSERT_EQ(0, r); + printf("finished aio compare and write\n"); + comp->release(); + *passed = true; +} + +void compare_and_write_test_data(librbd::Image& image, const char *cmp_data, const char *test_data, + off_t off, ssize_t len, uint64_t *mismatch_off, uint32_t iohint, bool *passed) +{ + size_t written; + ceph::bufferlist cmp_bl; + cmp_bl.append(cmp_data, strlen(cmp_data)); + ceph::bufferlist test_bl; + test_bl.append(test_data, strlen(test_data)); + printf("start compare and write\n"); + written = image.compare_and_write(off, len, cmp_bl, test_bl, mismatch_off, iohint); + printf("compare and wrote: %d\n", (int) written); + ASSERT_EQ(len, static_cast<ssize_t>(written)); + *passed = true; +} + +TEST_F(TestLibRBD, TestIOPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + bool skip_discard = is_skip_partial_discard_enabled(); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + char test_data[TEST_IO_SIZE + 1]; + char zero_data[TEST_IO_SIZE + 1]; + int i; + uint64_t mismatch_offset; + + for (i = 0; i < TEST_IO_SIZE; ++i) { + test_data[i] = (char) (rand() % (126 - 33) + 33); + } + test_data[TEST_IO_SIZE] = '\0'; + memset(zero_data, 0, sizeof(zero_data)); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(write_test_data, image, test_data, strlen(test_data) * i, 0); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_write_test_data, image, test_data, strlen(test_data) * i, 0); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(compare_and_write_test_data, image, test_data, test_data, TEST_IO_SIZE * i, + TEST_IO_SIZE, &mismatch_offset, 0); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_compare_and_write_test_data, image, test_data, test_data, TEST_IO_SIZE * i, + TEST_IO_SIZE, 0); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(read_test_data, image, test_data, strlen(test_data) * i, TEST_IO_SIZE, 0); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_read_test_data, image, test_data, strlen(test_data) * i, TEST_IO_SIZE, 0); + + // discard 2nd, 4th sections. + ASSERT_PASSED(discard_test_data, image, TEST_IO_SIZE, TEST_IO_SIZE); + ASSERT_PASSED(aio_discard_test_data, image, TEST_IO_SIZE*3, TEST_IO_SIZE); + + ASSERT_PASSED(read_test_data, image, test_data, 0, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, skip_discard ? test_data : zero_data, + TEST_IO_SIZE, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, test_data, TEST_IO_SIZE*2, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, skip_discard ? test_data : zero_data, + TEST_IO_SIZE*3, TEST_IO_SIZE, 0); + ASSERT_PASSED(read_test_data, image, test_data, TEST_IO_SIZE*4, TEST_IO_SIZE, 0); + + for (i = 0; i < 15; ++i) { + if (i % 3 == 2) { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, 0); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, 0); + } else if (i % 3 == 1) { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + } else { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + } + } + for (i = 0; i < 15; ++i) { + if (i % 3 == 2) { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, 0); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, 0); + } else if (i % 3 == 1) { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE + i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + } else { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE * i, TEST_IO_SIZE * i * 32, TEST_IO_SIZE, 0); + } + } + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestIOPPWithIOHint) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + char test_data[TEST_IO_SIZE + 1]; + char zero_data[TEST_IO_SIZE + 1]; + test_data[TEST_IO_SIZE] = '\0'; + int i; + + for (i = 0; i < TEST_IO_SIZE; ++i) { + test_data[i] = (char) (rand() % (126 - 33) + 33); + } + memset(zero_data, 0, sizeof(zero_data)); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(write_test_data, image, test_data, strlen(test_data) * i, + LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_write_test_data, image, test_data, strlen(test_data) * i, + LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + + ASSERT_PASSED(read_test_data, image, test_data, strlen(test_data), + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_RANDOM); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_read_test_data, image, test_data, strlen(test_data) * i, + TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL|LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + + for (i = 0; i < 15; ++i) { + if (i % 3 == 2) { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE * i, + TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE * i, + TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + } else if (i % 3 == 1) { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE + i, + TEST_IO_SIZE * i * 32, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE + i, + TEST_IO_SIZE * i * 32, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + } else { + ASSERT_PASSED(writesame_test_data, image, test_data, TEST_IO_SIZE * i, + TEST_IO_SIZE * i * 32, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + ASSERT_PASSED(writesame_test_data, image, zero_data, TEST_IO_SIZE * i, + TEST_IO_SIZE * i * 32, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + } + } + for (i = 0; i < 15; ++i) { + if (i % 3 == 2) { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE * i, + TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE * i, + TEST_IO_SIZE * i * 32 + i, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + } else if (i % 3 == 1) { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE + i, + TEST_IO_SIZE * i * 32, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE + i, + TEST_IO_SIZE * i * 32, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + } else { + ASSERT_PASSED(aio_writesame_test_data, image, test_data, TEST_IO_SIZE * i, + TEST_IO_SIZE * i * 32, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + ASSERT_PASSED(aio_writesame_test_data, image, zero_data, TEST_IO_SIZE * i, + TEST_IO_SIZE * i * 32, TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + } + } + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + + + +TEST_F(TestLibRBD, TestIOToSnapshot) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t isize = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), isize, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + int i, r; + rbd_image_t image_at_snap; + char orig_data[TEST_IO_TO_SNAP_SIZE + 1]; + char test_data[TEST_IO_TO_SNAP_SIZE + 1]; + + for (i = 0; i < TEST_IO_TO_SNAP_SIZE; ++i) + test_data[i] = (char) (i + 48); + test_data[TEST_IO_TO_SNAP_SIZE] = '\0'; + orig_data[TEST_IO_TO_SNAP_SIZE] = '\0'; + + r = rbd_read(image, 0, TEST_IO_TO_SNAP_SIZE, orig_data); + ASSERT_EQ(r, TEST_IO_TO_SNAP_SIZE); + + ASSERT_EQ(0, test_ls_snaps(image, 0)); + ASSERT_EQ(0, rbd_snap_create(image, "orig")); + ASSERT_EQ(1, test_ls_snaps(image, 1, "orig", isize)); + ASSERT_PASSED(read_test_data, image, orig_data, 0, TEST_IO_TO_SNAP_SIZE, 0); + + printf("write test data!\n"); + ASSERT_PASSED(write_test_data, image, test_data, 0, TEST_IO_TO_SNAP_SIZE, 0); + ASSERT_EQ(0, rbd_snap_create(image, "written")); + ASSERT_EQ(2, test_ls_snaps(image, 2, "orig", isize, "written", isize)); + + ASSERT_PASSED(read_test_data, image, test_data, 0, TEST_IO_TO_SNAP_SIZE, 0); + + rbd_snap_set(image, "orig"); + ASSERT_PASSED(read_test_data, image, orig_data, 0, TEST_IO_TO_SNAP_SIZE, 0); + + rbd_snap_set(image, "written"); + ASSERT_PASSED(read_test_data, image, test_data, 0, TEST_IO_TO_SNAP_SIZE, 0); + + rbd_snap_set(image, "orig"); + + r = rbd_write(image, 0, TEST_IO_TO_SNAP_SIZE, test_data); + printf("write to snapshot returned %d\n", r); + ASSERT_LT(r, 0); + cout << strerror(-r) << std::endl; + + ASSERT_PASSED(read_test_data, image, orig_data, 0, TEST_IO_TO_SNAP_SIZE, 0); + rbd_snap_set(image, "written"); + ASSERT_PASSED(read_test_data, image, test_data, 0, TEST_IO_TO_SNAP_SIZE, 0); + + r = rbd_snap_rollback(image, "orig"); + ASSERT_EQ(r, -EROFS); + + r = rbd_snap_set(image, NULL); + ASSERT_EQ(r, 0); + r = rbd_snap_rollback(image, "orig"); + ASSERT_EQ(r, 0); + + ASSERT_PASSED(write_test_data, image, test_data, 0, TEST_IO_TO_SNAP_SIZE, 0); + + rbd_flush(image); + + printf("opening testimg@orig\n"); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image_at_snap, "orig")); + ASSERT_PASSED(read_test_data, image_at_snap, orig_data, 0, TEST_IO_TO_SNAP_SIZE, 0); + r = rbd_write(image_at_snap, 0, TEST_IO_TO_SNAP_SIZE, test_data); + printf("write to snapshot returned %d\n", r); + ASSERT_LT(r, 0); + cout << strerror(-r) << std::endl; + ASSERT_EQ(0, rbd_close(image_at_snap)); + + ASSERT_EQ(2, test_ls_snaps(image, 2, "orig", isize, "written", isize)); + ASSERT_EQ(0, rbd_snap_remove(image, "written")); + ASSERT_EQ(1, test_ls_snaps(image, 1, "orig", isize)); + ASSERT_EQ(0, rbd_snap_remove(image, "orig")); + ASSERT_EQ(0, test_ls_snaps(image, 0)); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, TestClone) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + ASSERT_EQ(0, rados_conf_set(_cluster, "rbd_default_clone_format", "1")); + BOOST_SCOPE_EXIT_ALL(&) { + ASSERT_EQ(0, rados_conf_set(_cluster, "rbd_default_clone_format", "auto")); + }; + + rados_ioctx_t ioctx; + rbd_image_info_t pinfo, cinfo; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + bool old_format; + uint64_t features; + rbd_image_t parent, child; + int order = 0; + + ASSERT_EQ(0, get_features(&old_format, &features)); + ASSERT_FALSE(old_format); + + std::string parent_name = get_temp_image_name(); + std::string child_name = get_temp_image_name(); + + // make a parent to clone from + ASSERT_EQ(0, create_image_full(ioctx, parent_name.c_str(), 4<<20, &order, + false, features)); + ASSERT_EQ(0, rbd_open(ioctx, parent_name.c_str(), &parent, NULL)); + printf("made parent image \"parent\"\n"); + + char *data = (char *)"testdata"; + ASSERT_EQ((ssize_t)strlen(data), rbd_write(parent, 0, strlen(data), data)); + + // can't clone a non-snapshot, expect failure + EXPECT_NE(0, clone_image(ioctx, parent, parent_name.c_str(), NULL, ioctx, + child_name.c_str(), features, &order)); + + // verify that there is no parent info on "parent" + ASSERT_EQ(-ENOENT, rbd_get_parent_info(parent, NULL, 0, NULL, 0, NULL, 0)); + printf("parent has no parent info\n"); + + // create 70 metadatas to verify we can clone all key/value pairs + std::string key; + std::string val; + size_t sum_key_len = 0; + size_t sum_value_len = 0; + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_set(parent, key.c_str(), val.c_str())); + + sum_key_len += (key.size() + 1); + sum_value_len += (val.size() + 1); + } + + char keys[1024]; + char vals[1024]; + size_t keys_len = sizeof(keys); + size_t vals_len = sizeof(vals); + + char value[1024]; + size_t value_len = sizeof(value); + + // create a snapshot, reopen as the parent we're interested in + ASSERT_EQ(0, rbd_snap_create(parent, "parent_snap")); + printf("made snapshot \"parent@parent_snap\"\n"); + ASSERT_EQ(0, rbd_close(parent)); + ASSERT_EQ(0, rbd_open(ioctx, parent_name.c_str(), &parent, "parent_snap")); + + ASSERT_EQ(-EINVAL, clone_image(ioctx, parent, parent_name.c_str(), "parent_snap", + ioctx, child_name.c_str(), features, &order)); + + // unprotected image should fail unprotect + ASSERT_EQ(-EINVAL, rbd_snap_unprotect(parent, "parent_snap")); + printf("can't unprotect an unprotected snap\n"); + + ASSERT_EQ(0, rbd_snap_protect(parent, "parent_snap")); + // protecting again should fail + ASSERT_EQ(-EBUSY, rbd_snap_protect(parent, "parent_snap")); + printf("can't protect a protected snap\n"); + + // This clone and open should work + ASSERT_EQ(0, clone_image(ioctx, parent, parent_name.c_str(), "parent_snap", + ioctx, child_name.c_str(), features, &order)); + ASSERT_EQ(0, rbd_open(ioctx, child_name.c_str(), &child, NULL)); + printf("made and opened clone \"child\"\n"); + + // check read + ASSERT_PASSED(read_test_data, child, data, 0, strlen(data), 0); + + // check write + ASSERT_EQ((ssize_t)strlen(data), rbd_write(child, 20, strlen(data), data)); + ASSERT_PASSED(read_test_data, child, data, 20, strlen(data), 0); + ASSERT_PASSED(read_test_data, child, data, 0, strlen(data), 0); + + // check attributes + ASSERT_EQ(0, rbd_stat(parent, &pinfo, sizeof(pinfo))); + ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo))); + EXPECT_EQ(cinfo.size, pinfo.size); + uint64_t overlap; + rbd_get_overlap(child, &overlap); + EXPECT_EQ(overlap, pinfo.size); + EXPECT_EQ(cinfo.obj_size, pinfo.obj_size); + EXPECT_EQ(cinfo.order, pinfo.order); + printf("sizes and overlaps are good between parent and child\n"); + + // check key/value pairs in child image + ASSERT_EQ(0, rbd_metadata_list(child, "", 70, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(sum_key_len, keys_len); + ASSERT_EQ(sum_value_len, vals_len); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_get(child, key.c_str(), value, &value_len)); + ASSERT_STREQ(val.c_str(), value); + + value_len = sizeof(value); + } + printf("child image successfully cloned all image-meta pairs\n"); + + // sizing down child results in changing overlap and size, not parent size + ASSERT_EQ(0, rbd_resize(child, 2UL<<20)); + ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo))); + rbd_get_overlap(child, &overlap); + ASSERT_EQ(overlap, 2UL<<20); + ASSERT_EQ(cinfo.size, 2UL<<20); + ASSERT_EQ(0, rbd_resize(child, 4UL<<20)); + ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo))); + rbd_get_overlap(child, &overlap); + ASSERT_EQ(overlap, 2UL<<20); + ASSERT_EQ(cinfo.size, 4UL<<20); + printf("sized down clone, changed overlap\n"); + + // sizing back up doesn't change that + ASSERT_EQ(0, rbd_resize(child, 5UL<<20)); + ASSERT_EQ(0, rbd_stat(child, &cinfo, sizeof(cinfo))); + rbd_get_overlap(child, &overlap); + ASSERT_EQ(overlap, 2UL<<20); + ASSERT_EQ(cinfo.size, 5UL<<20); + ASSERT_EQ(0, rbd_stat(parent, &pinfo, sizeof(pinfo))); + printf("parent info: size %llu obj_size %llu parent_pool %llu\n", + (unsigned long long)pinfo.size, (unsigned long long)pinfo.obj_size, + (unsigned long long)pinfo.parent_pool); + ASSERT_EQ(pinfo.size, 4UL<<20); + printf("sized up clone, changed size but not overlap or parent's size\n"); + + ASSERT_PASSED(validate_object_map, child); + ASSERT_EQ(0, rbd_close(child)); + + ASSERT_PASSED(validate_object_map, parent); + ASSERT_EQ(-EBUSY, rbd_snap_remove(parent, "parent_snap")); + printf("can't remove parent while child still exists\n"); + ASSERT_EQ(0, rbd_remove(ioctx, child_name.c_str())); + ASSERT_EQ(-EBUSY, rbd_snap_remove(parent, "parent_snap")); + printf("can't remove parent while still protected\n"); + ASSERT_EQ(0, rbd_snap_unprotect(parent, "parent_snap")); + ASSERT_EQ(0, rbd_snap_remove(parent, "parent_snap")); + printf("removed parent snap after unprotecting\n"); + + ASSERT_EQ(0, rbd_close(parent)); + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, TestClone2) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + ASSERT_EQ(0, rados_conf_set(_cluster, "rbd_default_clone_format", "2")); + BOOST_SCOPE_EXIT_ALL(&) { + ASSERT_EQ(0, rados_conf_set(_cluster, "rbd_default_clone_format", "auto")); + }; + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + bool old_format; + uint64_t features; + rbd_image_t parent, child; + int order = 0; + + ASSERT_EQ(0, get_features(&old_format, &features)); + ASSERT_FALSE(old_format); + + std::string parent_name = get_temp_image_name(); + std::string child_name = get_temp_image_name(); + + // make a parent to clone from + ASSERT_EQ(0, create_image_full(ioctx, parent_name.c_str(), 4<<20, &order, + false, features)); + ASSERT_EQ(0, rbd_open(ioctx, parent_name.c_str(), &parent, NULL)); + printf("made parent image \"parent\"\n"); + + char *data = (char *)"testdata"; + char *childata = (char *)"childata"; + ASSERT_EQ((ssize_t)strlen(data), rbd_write(parent, 0, strlen(data), data)); + ASSERT_EQ((ssize_t)strlen(data), rbd_write(parent, 12, strlen(data), data)); + + // can't clone a non-snapshot, expect failure + EXPECT_NE(0, clone_image(ioctx, parent, parent_name.c_str(), NULL, ioctx, + child_name.c_str(), features, &order)); + + // verify that there is no parent info on "parent" + ASSERT_EQ(-ENOENT, rbd_get_parent_info(parent, NULL, 0, NULL, 0, NULL, 0)); + printf("parent has no parent info\n"); + + // create 70 metadatas to verify we can clone all key/value pairs + std::string key; + std::string val; + size_t sum_key_len = 0; + size_t sum_value_len = 0; + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_set(parent, key.c_str(), val.c_str())); + + sum_key_len += (key.size() + 1); + sum_value_len += (val.size() + 1); + } + + char keys[1024]; + char vals[1024]; + size_t keys_len = sizeof(keys); + size_t vals_len = sizeof(vals); + + char value[1024]; + size_t value_len = sizeof(value); + + // create a snapshot, reopen as the parent we're interested in + ASSERT_EQ(0, rbd_snap_create(parent, "parent_snap")); + printf("made snapshot \"parent@parent_snap\"\n"); + ASSERT_EQ(0, rbd_close(parent)); + ASSERT_EQ(0, rbd_open(ioctx, parent_name.c_str(), &parent, "parent_snap")); + + // This clone and open should work + ASSERT_EQ(0, clone_image(ioctx, parent, parent_name.c_str(), "parent_snap", + ioctx, child_name.c_str(), features, &order)); + ASSERT_EQ(0, rbd_open(ioctx, child_name.c_str(), &child, NULL)); + printf("made and opened clone \"child\"\n"); + + // check key/value pairs in child image + ASSERT_EQ(0, rbd_metadata_list(child, "", 70, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(sum_key_len, keys_len); + ASSERT_EQ(sum_value_len, vals_len); + + for (int i = 1; i <= 70; i++) { + key = "key" + stringify(i); + val = "value" + stringify(i); + ASSERT_EQ(0, rbd_metadata_get(child, key.c_str(), value, &value_len)); + ASSERT_STREQ(val.c_str(), value); + + value_len = sizeof(value); + } + printf("child image successfully cloned all image-meta pairs\n"); + + // write something in + ASSERT_EQ((ssize_t)strlen(childata), rbd_write(child, 20, strlen(childata), childata)); + + char test[strlen(data) * 2]; + ASSERT_EQ((ssize_t)strlen(data), rbd_read(child, 20, strlen(data), test)); + ASSERT_EQ(0, memcmp(test, childata, strlen(childata))); + + // overlap + ASSERT_EQ((ssize_t)sizeof(test), rbd_read(child, 20 - strlen(data), sizeof(test), test)); + ASSERT_EQ(0, memcmp(test, data, strlen(data))); + ASSERT_EQ(0, memcmp(test + strlen(data), childata, strlen(childata))); + + // all parent + ASSERT_EQ((ssize_t)sizeof(test), rbd_read(child, 0, sizeof(test), test)); + ASSERT_EQ(0, memcmp(test, data, strlen(data))); + + ASSERT_PASSED(validate_object_map, child); + ASSERT_PASSED(validate_object_map, parent); + + rbd_snap_info_t snaps[2]; + int max_snaps = 2; + ASSERT_EQ(1, rbd_snap_list(parent, snaps, &max_snaps)); + rbd_snap_list_end(snaps); + + ASSERT_EQ(0, rbd_snap_remove_by_id(parent, snaps[0].id)); + + rbd_snap_namespace_type_t snap_namespace_type; + ASSERT_EQ(0, rbd_snap_get_namespace_type(parent, snaps[0].id, + &snap_namespace_type)); + ASSERT_EQ(RBD_SNAP_NAMESPACE_TYPE_TRASH, snap_namespace_type); + + char original_name[32]; + ASSERT_EQ(0, rbd_snap_get_trash_namespace(parent, snaps[0].id, + original_name, + sizeof(original_name))); + ASSERT_EQ(0, strcmp("parent_snap", original_name)); + + ASSERT_EQ(0, rbd_close(child)); + ASSERT_EQ(0, rbd_close(parent)); + rados_ioctx_destroy(ioctx); +} + +static void test_list_children(rbd_image_t image, ssize_t num_expected, ...) +{ + va_list ap; + va_start(ap, num_expected); + size_t pools_len = 100; + size_t children_len = 100; + char *pools = NULL; + char *children = NULL; + ssize_t num_children; + + do { + free(pools); + free(children); + pools = (char *) malloc(pools_len); + children = (char *) malloc(children_len); + num_children = rbd_list_children(image, pools, &pools_len, + children, &children_len); + } while (num_children == -ERANGE); + + ASSERT_EQ(num_expected, num_children); + for (ssize_t i = num_expected; i > 0; --i) { + char *expected_pool = va_arg(ap, char *); + char *expected_image = va_arg(ap, char *); + char *pool = pools; + char *image = children; + bool found = 0; + printf("\ntrying to find %s/%s\n", expected_pool, expected_image); + for (ssize_t j = 0; j < num_children; ++j) { + printf("checking %s/%s\n", pool, image); + if (strcmp(expected_pool, pool) == 0 && + strcmp(expected_image, image) == 0) { + printf("found child %s/%s\n\n", pool, image); + found = 1; + break; + } + pool += strlen(pool) + 1; + image += strlen(image) + 1; + if (j == num_children - 1) { + ASSERT_EQ(pool - pools - 1, (ssize_t) pools_len); + ASSERT_EQ(image - children - 1, (ssize_t) children_len); + } + } + ASSERT_TRUE(found); + } + va_end(ap); + + if (pools) + free(pools); + if (children) + free(children); +} + +static void test_list_children2(rbd_image_t image, int num_expected, ...) +{ + int num_children, i, j, max_size = 10; + va_list ap; + rbd_child_info_t children[max_size]; + num_children = rbd_list_children2(image, children, &max_size); + printf("num children is: %d\nexpected: %d\n", num_children, num_expected); + + for (i = 0; i < num_children; i++) { + printf("child: %s\n", children[i].image_name); + } + + va_start(ap, num_expected); + for (i = num_expected; i > 0; i--) { + char *expected_id = va_arg(ap, char *); + char *expected_pool = va_arg(ap, char *); + char *expected_image = va_arg(ap, char *); + bool expected_trash = va_arg(ap, int); + bool found = false; + for (j = 0; j < num_children; j++) { + if (children[j].pool_name == NULL || + children[j].image_name == NULL || + children[j].image_id == NULL) + continue; + if (strcmp(children[j].image_id, expected_id) == 0 && + strcmp(children[j].pool_name, expected_pool) == 0 && + strcmp(children[j].image_name, expected_image) == 0 && + children[j].trash == expected_trash) { + printf("found child %s/%s/%s\n\n", children[j].pool_name, children[j].image_name, children[j].image_id); + rbd_list_child_cleanup(&children[j]); + children[j].pool_name = NULL; + children[j].image_name = NULL; + children[j].image_id = NULL; + found = true; + break; + } + } + EXPECT_TRUE(found); + } + va_end(ap); + + for (i = 0; i < num_children; i++) { + EXPECT_EQ((const char *)0, children[i].pool_name); + EXPECT_EQ((const char *)0, children[i].image_name); + EXPECT_EQ((const char *)0, children[i].image_id); + } +} + +TEST_F(TestLibRBD, ListChildren) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::RBD rbd; + rados_ioctx_t ioctx1, ioctx2; + string pool_name1 = create_pool(true); + string pool_name2 = create_pool(true); + ASSERT_NE("", pool_name2); + + rados_ioctx_create(_cluster, pool_name1.c_str(), &ioctx1); + rados_ioctx_create(_cluster, pool_name2.c_str(), &ioctx2); + + rbd_image_t image1; + rbd_image_t image2; + rbd_image_t image3; + rbd_image_t image4; + + bool old_format; + uint64_t features; + rbd_image_t parent; + int order = 0; + + ASSERT_EQ(0, get_features(&old_format, &features)); + ASSERT_FALSE(old_format); + + std::string parent_name = get_temp_image_name(); + std::string child_name1 = get_temp_image_name(); + std::string child_name2 = get_temp_image_name(); + std::string child_name3 = get_temp_image_name(); + std::string child_name4 = get_temp_image_name(); + + char child_id1[4096]; + char child_id2[4096]; + char child_id3[4096]; + char child_id4[4096]; + + // make a parent to clone from + ASSERT_EQ(0, create_image_full(ioctx1, parent_name.c_str(), 4<<20, &order, + false, features)); + ASSERT_EQ(0, rbd_open(ioctx1, parent_name.c_str(), &parent, NULL)); + // create a snapshot, reopen as the parent we're interested in + ASSERT_EQ(0, rbd_snap_create(parent, "parent_snap")); + ASSERT_EQ(0, rbd_snap_set(parent, "parent_snap")); + ASSERT_EQ(0, rbd_snap_protect(parent, "parent_snap")); + + ASSERT_EQ(0, rbd_close(parent)); + ASSERT_EQ(0, rbd_open(ioctx1, parent_name.c_str(), &parent, "parent_snap")); + + ASSERT_EQ(0, clone_image(ioctx1, parent, parent_name.c_str(), "parent_snap", + ioctx2, child_name1.c_str(), features, &order)); + ASSERT_EQ(0, rbd_open(ioctx2, child_name1.c_str(), &image1, NULL)); + ASSERT_EQ(0, rbd_get_id(image1, child_id1, sizeof(child_id1))); + test_list_children(parent, 1, pool_name2.c_str(), child_name1.c_str()); + test_list_children2(parent, 1, + child_id1, pool_name2.c_str(), child_name1.c_str(), false); + + ASSERT_EQ(0, clone_image(ioctx1, parent, parent_name.c_str(), "parent_snap", + ioctx1, child_name2.c_str(), features, &order)); + ASSERT_EQ(0, rbd_open(ioctx1, child_name2.c_str(), &image2, NULL)); + ASSERT_EQ(0, rbd_get_id(image2, child_id2, sizeof(child_id2))); + test_list_children(parent, 2, pool_name2.c_str(), child_name1.c_str(), + pool_name1.c_str(), child_name2.c_str()); + test_list_children2(parent, 2, + child_id1, pool_name2.c_str(), child_name1.c_str(), false, + child_id2, pool_name1.c_str(), child_name2.c_str(), false); + + ASSERT_EQ(0, clone_image(ioctx1, parent, parent_name.c_str(), "parent_snap", + ioctx2, child_name3.c_str(), features, &order)); + ASSERT_EQ(0, rbd_open(ioctx2, child_name3.c_str(), &image3, NULL)); + ASSERT_EQ(0, rbd_get_id(image3, child_id3, sizeof(child_id3))); + test_list_children(parent, 3, pool_name2.c_str(), child_name1.c_str(), + pool_name1.c_str(), child_name2.c_str(), + pool_name2.c_str(), child_name3.c_str()); + test_list_children2(parent, 3, + child_id1, pool_name2.c_str(), child_name1.c_str(), false, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id3, pool_name2.c_str(), child_name3.c_str(), false); + + librados::IoCtx ioctx3; + ASSERT_EQ(0, _rados.ioctx_create(pool_name2.c_str(), ioctx3)); + ASSERT_EQ(0, rbd_close(image3)); + ASSERT_EQ(0, rbd.trash_move(ioctx3, child_name3.c_str(), 0)); + test_list_children(parent, 2, pool_name2.c_str(), child_name1.c_str(), + pool_name1.c_str(), child_name2.c_str()); + test_list_children2(parent, 3, + child_id1, pool_name2.c_str(), child_name1.c_str(), false, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id3, pool_name2.c_str(), child_name3.c_str(), true); + + ASSERT_EQ(0, clone_image(ioctx1, parent, parent_name.c_str(), "parent_snap", + ioctx2, child_name4.c_str(), features, &order)); + ASSERT_EQ(0, rbd_open(ioctx2, child_name4.c_str(), &image4, NULL)); + ASSERT_EQ(0, rbd_get_id(image4, child_id4, sizeof(child_id4))); + test_list_children(parent, 3, pool_name2.c_str(), child_name1.c_str(), + pool_name1.c_str(), child_name2.c_str(), + pool_name2.c_str(), child_name4.c_str()); + test_list_children2(parent, 4, + child_id1, pool_name2.c_str(), child_name1.c_str(), false, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id3, pool_name2.c_str(), child_name3.c_str(), true, + child_id4, pool_name2.c_str(), child_name4.c_str(), false); + + ASSERT_EQ(0, rbd.trash_restore(ioctx3, child_id3, "")); + test_list_children(parent, 4, pool_name2.c_str(), child_name1.c_str(), + pool_name1.c_str(), child_name2.c_str(), + pool_name2.c_str(), child_name3.c_str(), + pool_name2.c_str(), child_name4.c_str()); + test_list_children2(parent, 4, + child_id1, pool_name2.c_str(), child_name1.c_str(), false, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id3, pool_name2.c_str(), child_name3.c_str(), false, + child_id4, pool_name2.c_str(), child_name4.c_str(), false); + + ASSERT_EQ(0, rbd_close(image1)); + ASSERT_EQ(0, rbd_remove(ioctx2, child_name1.c_str())); + test_list_children(parent, 3, + pool_name1.c_str(), child_name2.c_str(), + pool_name2.c_str(), child_name3.c_str(), + pool_name2.c_str(), child_name4.c_str()); + test_list_children2(parent, 3, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id3, pool_name2.c_str(), child_name3.c_str(), false, + child_id4, pool_name2.c_str(), child_name4.c_str(), false); + + ASSERT_EQ(0, rbd_remove(ioctx2, child_name3.c_str())); + test_list_children(parent, 2, + pool_name1.c_str(), child_name2.c_str(), + pool_name2.c_str(), child_name4.c_str()); + test_list_children2(parent, 2, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id4, pool_name2.c_str(), child_name4.c_str(), false); + + ASSERT_EQ(0, rbd_close(image4)); + ASSERT_EQ(0, rbd_remove(ioctx2, child_name4.c_str())); + test_list_children(parent, 1, + pool_name1.c_str(), child_name2.c_str()); + test_list_children2(parent, 1, + child_id2, pool_name1.c_str(), child_name2.c_str(), false); + + + ASSERT_EQ(0, rbd_close(image2)); + ASSERT_EQ(0, rbd_remove(ioctx1, child_name2.c_str())); + test_list_children(parent, 0); + test_list_children2(parent, 0); + + ASSERT_EQ(0, rbd_snap_unprotect(parent, "parent_snap")); + ASSERT_EQ(0, rbd_snap_remove(parent, "parent_snap")); + ASSERT_EQ(0, rbd_close(parent)); + ASSERT_EQ(0, rbd_remove(ioctx1, parent_name.c_str())); + rados_ioctx_destroy(ioctx1); + rados_ioctx_destroy(ioctx2); +} + +TEST_F(TestLibRBD, ListChildrenTiered) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::RBD rbd; + string pool_name1 = create_pool(true); + string pool_name2 = create_pool(true); + string pool_name3 = create_pool(true); + ASSERT_NE("", pool_name1); + ASSERT_NE("", pool_name2); + ASSERT_NE("", pool_name3); + + std::string cmdstr = "{\"prefix\": \"osd tier add\", \"pool\": \"" + + pool_name1 + "\", \"tierpool\":\"" + pool_name3 + "\", \"force_nonempty\":\"\"}"; + char *cmd[1]; + cmd[0] = (char *)cmdstr.c_str(); + ASSERT_EQ(0, rados_mon_command(_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0)); + + cmdstr = "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + + pool_name3 + "\", \"mode\":\"writeback\"}"; + cmd[0] = (char *)cmdstr.c_str(); + ASSERT_EQ(0, rados_mon_command(_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0)); + + cmdstr = "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + + pool_name1 + "\", \"overlaypool\":\"" + pool_name3 + "\"}"; + cmd[0] = (char *)cmdstr.c_str(); + ASSERT_EQ(0, rados_mon_command(_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0)); + + EXPECT_EQ(0, rados_wait_for_latest_osdmap(_cluster)); + + string parent_name = get_temp_image_name(); + string child_name1 = get_temp_image_name(); + string child_name2 = get_temp_image_name(); + string child_name3 = get_temp_image_name(); + string child_name4 = get_temp_image_name(); + + char child_id1[4096]; + char child_id2[4096]; + char child_id3[4096]; + char child_id4[4096]; + + rbd_image_t image1; + rbd_image_t image2; + rbd_image_t image3; + rbd_image_t image4; + + rados_ioctx_t ioctx1, ioctx2; + rados_ioctx_create(_cluster, pool_name1.c_str(), &ioctx1); + rados_ioctx_create(_cluster, pool_name2.c_str(), &ioctx2); + + bool old_format; + uint64_t features; + rbd_image_t parent; + int order = 0; + + ASSERT_EQ(0, get_features(&old_format, &features)); + ASSERT_FALSE(old_format); + + // make a parent to clone from + ASSERT_EQ(0, create_image_full(ioctx1, parent_name.c_str(), 4<<20, &order, + false, features)); + ASSERT_EQ(0, rbd_open(ioctx1, parent_name.c_str(), &parent, NULL)); + // create a snapshot, reopen as the parent we're interested in + ASSERT_EQ(0, rbd_snap_create(parent, "parent_snap")); + ASSERT_EQ(0, rbd_snap_set(parent, "parent_snap")); + ASSERT_EQ(0, rbd_snap_protect(parent, "parent_snap")); + + ASSERT_EQ(0, rbd_close(parent)); + ASSERT_EQ(0, rbd_open(ioctx1, parent_name.c_str(), &parent, "parent_snap")); + + ASSERT_EQ(0, clone_image(ioctx1, parent, parent_name.c_str(), "parent_snap", + ioctx2, child_name1.c_str(), features, &order)); + ASSERT_EQ(0, rbd_open(ioctx2, child_name1.c_str(), &image1, NULL)); + ASSERT_EQ(0, rbd_get_id(image1, child_id1, sizeof(child_id1))); + test_list_children(parent, 1, pool_name2.c_str(), child_name1.c_str()); + test_list_children2(parent, 1, + child_id1, pool_name2.c_str(), child_name1.c_str(), false); + + ASSERT_EQ(0, clone_image(ioctx1, parent, parent_name.c_str(), "parent_snap", + ioctx1, child_name2.c_str(), features, &order)); + ASSERT_EQ(0, rbd_open(ioctx1, child_name2.c_str(), &image2, NULL)); + ASSERT_EQ(0, rbd_get_id(image2, child_id2, sizeof(child_id2))); + test_list_children(parent, 2, pool_name2.c_str(), child_name1.c_str(), + pool_name1.c_str(), child_name2.c_str()); + test_list_children2(parent, 2, + child_id1, pool_name2.c_str(), child_name1.c_str(), false, + child_id2, pool_name1.c_str(), child_name2.c_str(), false); + + // read from the cache to populate it + rbd_image_t tier_image; + ASSERT_EQ(0, rbd_open(ioctx1, child_name2.c_str(), &tier_image, NULL)); + size_t len = 4 * 1024 * 1024; + char* buf = (char*)malloc(len); + ssize_t size = rbd_read(tier_image, 0, len, buf); + ASSERT_GT(size, 0); + free(buf); + ASSERT_EQ(0, rbd_close(tier_image)); + + ASSERT_EQ(0, clone_image(ioctx1, parent, parent_name.c_str(), "parent_snap", + ioctx2, child_name3.c_str(), features, &order)); + ASSERT_EQ(0, rbd_open(ioctx2, child_name3.c_str(), &image3, NULL)); + ASSERT_EQ(0, rbd_get_id(image3, child_id3, sizeof(child_id3))); + test_list_children(parent, 3, pool_name2.c_str(), child_name1.c_str(), + pool_name1.c_str(), child_name2.c_str(), + pool_name2.c_str(), child_name3.c_str()); + test_list_children2(parent, 3, + child_id1, pool_name2.c_str(), child_name1.c_str(), false, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id3, pool_name2.c_str(), child_name3.c_str(), false); + + librados::IoCtx ioctx3; + ASSERT_EQ(0, _rados.ioctx_create(pool_name2.c_str(), ioctx3)); + ASSERT_EQ(0, rbd_close(image3)); + ASSERT_EQ(0, rbd.trash_move(ioctx3, child_name3.c_str(), 0)); + test_list_children(parent, 2, pool_name2.c_str(), child_name1.c_str(), + pool_name1.c_str(), child_name2.c_str()); + test_list_children2(parent, 3, + child_id1, pool_name2.c_str(), child_name1.c_str(), false, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id3, pool_name2.c_str(), child_name3.c_str(), true); + + ASSERT_EQ(0, clone_image(ioctx1, parent, parent_name.c_str(), "parent_snap", + ioctx2, child_name4.c_str(), features, &order)); + ASSERT_EQ(0, rbd_open(ioctx2, child_name4.c_str(), &image4, NULL)); + ASSERT_EQ(0, rbd_get_id(image4, child_id4, sizeof(child_id4))); + test_list_children(parent, 3, pool_name2.c_str(), child_name1.c_str(), + pool_name1.c_str(), child_name2.c_str(), + pool_name2.c_str(), child_name4.c_str()); + test_list_children2(parent, 4, + child_id1, pool_name2.c_str(), child_name1.c_str(), false, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id3, pool_name2.c_str(), child_name3.c_str(), true, + child_id4, pool_name2.c_str(), child_name4.c_str(), false); + + ASSERT_EQ(0, rbd.trash_restore(ioctx3, child_id3, "")); + test_list_children(parent, 4, pool_name2.c_str(), child_name1.c_str(), + pool_name1.c_str(), child_name2.c_str(), + pool_name2.c_str(), child_name3.c_str(), + pool_name2.c_str(), child_name4.c_str()); + test_list_children2(parent, 4, + child_id1, pool_name2.c_str(), child_name1.c_str(), false, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id3, pool_name2.c_str(), child_name3.c_str(), false, + child_id4, pool_name2.c_str(), child_name4.c_str(), false); + + ASSERT_EQ(0, rbd_close(image1)); + ASSERT_EQ(0, rbd_remove(ioctx2, child_name1.c_str())); + test_list_children(parent, 3, + pool_name1.c_str(), child_name2.c_str(), + pool_name2.c_str(), child_name3.c_str(), + pool_name2.c_str(), child_name4.c_str()); + test_list_children2(parent, 3, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id3, pool_name2.c_str(), child_name3.c_str(), false, + child_id4, pool_name2.c_str(), child_name4.c_str(), false); + + ASSERT_EQ(0, rbd_remove(ioctx2, child_name3.c_str())); + test_list_children(parent, 2, + pool_name1.c_str(), child_name2.c_str(), + pool_name2.c_str(), child_name4.c_str()); + test_list_children2(parent, 2, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id4, pool_name2.c_str(), child_name4.c_str(), false); + + ASSERT_EQ(0, rbd_close(image4)); + ASSERT_EQ(0, rbd_remove(ioctx2, child_name4.c_str())); + test_list_children(parent, 1, + pool_name1.c_str(), child_name2.c_str()); + test_list_children2(parent, 1, + child_id2, pool_name1.c_str(), child_name2.c_str(), false); + + ASSERT_EQ(0, rbd_close(image2)); + ASSERT_EQ(0, rbd_remove(ioctx1, child_name2.c_str())); + test_list_children(parent, 0); + test_list_children2(parent, 0); + + ASSERT_EQ(0, rbd_snap_unprotect(parent, "parent_snap")); + ASSERT_EQ(0, rbd_snap_remove(parent, "parent_snap")); + ASSERT_EQ(0, rbd_close(parent)); + ASSERT_EQ(0, rbd_remove(ioctx1, parent_name.c_str())); + rados_ioctx_destroy(ioctx1); + rados_ioctx_destroy(ioctx2); + cmdstr = "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + + pool_name1 + "\"}"; + cmd[0] = (char *)cmdstr.c_str(); + ASSERT_EQ(0, rados_mon_command(_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0)); + cmdstr = "{\"prefix\": \"osd tier remove\", \"pool\": \"" + + pool_name1 + "\", \"tierpool\":\"" + pool_name3 + "\"}"; + cmd[0] = (char *)cmdstr.c_str(); + ASSERT_EQ(0, rados_mon_command(_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0)); +} + +TEST_F(TestLibRBD, LockingPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + std::string cookie1 = "foo"; + std::string cookie2 = "bar"; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + // no lockers initially + std::list<librbd::locker_t> lockers; + std::string tag; + bool exclusive; + ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag)); + ASSERT_EQ(0u, lockers.size()); + ASSERT_EQ("", tag); + + // exclusive lock is exclusive + ASSERT_EQ(0, image.lock_exclusive(cookie1)); + ASSERT_EQ(-EEXIST, image.lock_exclusive(cookie1)); + ASSERT_EQ(-EBUSY, image.lock_exclusive("")); + ASSERT_EQ(-EEXIST, image.lock_shared(cookie1, "")); + ASSERT_EQ(-EBUSY, image.lock_shared(cookie1, "test")); + ASSERT_EQ(-EBUSY, image.lock_shared("", "test")); + ASSERT_EQ(-EBUSY, image.lock_shared("", "")); + + // list exclusive + ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag)); + ASSERT_TRUE(exclusive); + ASSERT_EQ("", tag); + ASSERT_EQ(1u, lockers.size()); + ASSERT_EQ(cookie1, lockers.front().cookie); + + // unlock + ASSERT_EQ(-ENOENT, image.unlock("")); + ASSERT_EQ(-ENOENT, image.unlock(cookie2)); + ASSERT_EQ(0, image.unlock(cookie1)); + ASSERT_EQ(-ENOENT, image.unlock(cookie1)); + ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag)); + ASSERT_EQ(0u, lockers.size()); + + ASSERT_EQ(0, image.lock_shared(cookie1, "")); + ASSERT_EQ(-EEXIST, image.lock_shared(cookie1, "")); + ASSERT_EQ(0, image.lock_shared(cookie2, "")); + ASSERT_EQ(-EEXIST, image.lock_shared(cookie2, "")); + ASSERT_EQ(-EEXIST, image.lock_exclusive(cookie1)); + ASSERT_EQ(-EEXIST, image.lock_exclusive(cookie2)); + ASSERT_EQ(-EBUSY, image.lock_exclusive("")); + ASSERT_EQ(-EBUSY, image.lock_exclusive("test")); + + // list shared + ASSERT_EQ(0, image.list_lockers(&lockers, &exclusive, &tag)); + ASSERT_EQ(2u, lockers.size()); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, FlushAio) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + size_t num_aios = 256; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + char test_data[TEST_IO_SIZE + 1]; + size_t i; + for (i = 0; i < TEST_IO_SIZE; ++i) { + test_data[i] = (char) (rand() % (126 - 33) + 33); + } + + rbd_completion_t write_comps[num_aios]; + for (i = 0; i < num_aios; ++i) { + ASSERT_EQ(0, rbd_aio_create_completion(NULL, NULL, &write_comps[i])); + uint64_t offset = rand() % (size - TEST_IO_SIZE); + ASSERT_EQ(0, rbd_aio_write(image, offset, TEST_IO_SIZE, test_data, + write_comps[i])); + } + + rbd_completion_t flush_comp; + ASSERT_EQ(0, rbd_aio_create_completion(NULL, NULL, &flush_comp)); + ASSERT_EQ(0, rbd_aio_flush(image, flush_comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(flush_comp)); + ASSERT_EQ(1, rbd_aio_is_complete(flush_comp)); + rbd_aio_release(flush_comp); + + for (i = 0; i < num_aios; ++i) { + ASSERT_EQ(1, rbd_aio_is_complete(write_comps[i])); + rbd_aio_release(write_comps[i]); + } + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + ASSERT_EQ(0, rbd_remove(ioctx, name.c_str())); + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, FlushAioPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + const size_t num_aios = 256; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + char test_data[TEST_IO_SIZE + 1]; + size_t i; + for (i = 0; i < TEST_IO_SIZE; ++i) { + test_data[i] = (char) (rand() % (126 - 33) + 33); + } + test_data[TEST_IO_SIZE] = '\0'; + + librbd::RBD::AioCompletion *write_comps[num_aios]; + ceph::bufferlist bls[num_aios]; + for (i = 0; i < num_aios; ++i) { + bls[i].append(test_data, strlen(test_data)); + write_comps[i] = new librbd::RBD::AioCompletion(NULL, NULL); + uint64_t offset = rand() % (size - TEST_IO_SIZE); + ASSERT_EQ(0, image.aio_write(offset, TEST_IO_SIZE, bls[i], + write_comps[i])); + } + + librbd::RBD::AioCompletion *flush_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, image.aio_flush(flush_comp)); + ASSERT_EQ(0, flush_comp->wait_for_complete()); + ASSERT_EQ(1, flush_comp->is_complete()); + flush_comp->release(); + + for (i = 0; i < num_aios; ++i) { + librbd::RBD::AioCompletion *comp = write_comps[i]; + ASSERT_EQ(1, comp->is_complete()); + comp->release(); + } + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + + +int iterate_cb(uint64_t off, size_t len, int exists, void *arg) +{ + //cout << "iterate_cb " << off << "~" << len << std::endl; + interval_set<uint64_t> *diff = static_cast<interval_set<uint64_t> *>(arg); + diff->insert(off, len); + return 0; +} + +static int iterate_error_cb(uint64_t off, size_t len, int exists, void *arg) +{ + return -EINVAL; +} + +void scribble(librbd::Image& image, int n, int max, bool skip_discard, + interval_set<uint64_t> *exists, + interval_set<uint64_t> *what) +{ + uint64_t size; + image.size(&size); + interval_set<uint64_t> exists_at_start = *exists; + + for (int i=0; i<n; i++) { + uint64_t off = rand() % (size - max + 1); + uint64_t len = 1 + rand() % max; + if (!skip_discard && rand() % 4 == 0) { + ASSERT_EQ((int)len, image.discard(off, len)); + interval_set<uint64_t> w; + w.insert(off, len); + + // the zeroed bit no longer exists... + w.intersection_of(*exists); + exists->subtract(w); + + // the bits we discarded are no long written... + interval_set<uint64_t> w2 = w; + w2.intersection_of(*what); + what->subtract(w2); + + // except for the extents that existed at the start that we overwrote. + interval_set<uint64_t> w3; + w3.insert(off, len); + w3.intersection_of(exists_at_start); + what->union_of(w3); + + } else { + bufferlist bl; + bl.append(buffer::create(len)); + bl.zero(); + ASSERT_EQ((int)len, image.write(off, len, bl)); + interval_set<uint64_t> w; + w.insert(off, len); + what->union_of(w); + exists->union_of(w); + } + } +} + +interval_set<uint64_t> round_diff_interval(const interval_set<uint64_t>& diff, + uint64_t object_size) +{ + if (object_size == 0) { + return diff; + } + + interval_set<uint64_t> rounded_diff; + for (interval_set<uint64_t>::const_iterator it = diff.begin(); + it != diff.end(); ++it) { + uint64_t off = it.get_start(); + uint64_t len = it.get_len(); + off -= off % object_size; + len += (object_size - (len % object_size)); + interval_set<uint64_t> interval; + interval.insert(off, len); + rounded_diff.union_of(interval); + } + return rounded_diff; +} + +template <typename T> +class DiffIterateTest : public TestLibRBD { +public: + static const uint8_t whole_object = T::whole_object; +}; + +template <bool _whole_object> +class DiffIterateParams { +public: + static const uint8_t whole_object = _whole_object; +}; + +typedef ::testing::Types<DiffIterateParams<false>, + DiffIterateParams<true> > DiffIterateTypes; +TYPED_TEST_CASE(DiffIterateTest, DiffIterateTypes); + +TYPED_TEST(DiffIterateTest, DiffIterate) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); + + bool skip_discard = this->is_skip_partial_discard_enabled(); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = this->get_temp_image_name(); + uint64_t size = 20 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint64_t object_size = 0; + if (this->whole_object) { + object_size = 1 << order; + } + + interval_set<uint64_t> exists; + interval_set<uint64_t> one, two; + scribble(image, 10, 102400, skip_discard, &exists, &one); + cout << " wrote " << one << std::endl; + ASSERT_EQ(0, image.snap_create("one")); + scribble(image, 10, 102400, skip_discard, &exists, &two); + + two = round_diff_interval(two, object_size); + cout << " wrote " << two << std::endl; + + interval_set<uint64_t> diff; + ASSERT_EQ(0, image.diff_iterate2("one", 0, size, true, this->whole_object, + iterate_cb, (void *)&diff)); + cout << " diff was " << diff << std::endl; + if (!two.subset_of(diff)) { + interval_set<uint64_t> i; + i.intersection_of(two, diff); + interval_set<uint64_t> l = two; + l.subtract(i); + cout << " ... two - (two*diff) = " << l << std::endl; + } + ASSERT_TRUE(two.subset_of(diff)); + } + ioctx.close(); +} + +struct diff_extent { + diff_extent(uint64_t _offset, uint64_t _length, bool _exists, + uint64_t object_size) : + offset(_offset), length(_length), exists(_exists) + { + if (object_size != 0) { + offset -= offset % object_size; + length = object_size; + } + } + uint64_t offset; + uint64_t length; + bool exists; + bool operator==(const diff_extent& o) const { + return offset == o.offset && length == o.length && exists == o.exists; + } +}; + +ostream& operator<<(ostream & o, const diff_extent& e) { + return o << '(' << e.offset << '~' << e.length << ' ' << (e.exists ? "true" : "false") << ')'; +} + +int vector_iterate_cb(uint64_t off, size_t len, int exists, void *arg) +{ + cout << "iterate_cb " << off << "~" << len << std::endl; + vector<diff_extent> *diff = static_cast<vector<diff_extent> *>(arg); + diff->push_back(diff_extent(off, len, exists, 0)); + return 0; +} + +TYPED_TEST(DiffIterateTest, DiffIterateDiscard) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = this->get_temp_image_name(); + uint64_t size = 20 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint64_t object_size = 0; + if (this->whole_object) { + object_size = 1 << order; + } + vector<diff_extent> extents; + ceph::bufferlist bl; + + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, (void *) &extents)); + ASSERT_EQ(0u, extents.size()); + + char data[256]; + memset(data, 1, sizeof(data)); + bl.append(data, 256); + ASSERT_EQ(256, image.write(0, 256, bl)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, (void *) &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + + int obj_ofs = 256; + ASSERT_EQ(1 << order, image.discard(0, 1 << order)); + + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, (void *) &extents)); + ASSERT_EQ(0u, extents.size()); + + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ(256, image.write(0, 256, bl)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, (void *) &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(0, image.snap_create("snap2")); + + ASSERT_EQ(obj_ofs, image.discard(0, obj_ofs)); + + extents.clear(); + ASSERT_EQ(0, image.snap_set("snap2")); + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, (void *) &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + + ASSERT_EQ(0, image.snap_set(NULL)); + ASSERT_EQ(1 << order, image.discard(0, 1 << order)); + ASSERT_EQ(0, image.snap_create("snap3")); + ASSERT_EQ(0, image.snap_set("snap3")); + + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, (void *) &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, false, object_size), extents[0]); + ASSERT_PASSED(this->validate_object_map, image); +} + +TYPED_TEST(DiffIterateTest, DiffIterateStress) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); + + bool skip_discard = this->is_skip_partial_discard_enabled(); + + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = this->get_temp_image_name(); + uint64_t size = 400 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint64_t object_size = 0; + if (this->whole_object) { + object_size = 1 << order; + } + + interval_set<uint64_t> curexists; + vector<interval_set<uint64_t> > wrote; + vector<interval_set<uint64_t> > exists; + vector<string> snap; + int n = 20; + for (int i=0; i<n; i++) { + interval_set<uint64_t> w; + scribble(image, 10, 8192000, skip_discard, &curexists, &w); + cout << " i=" << i << " exists " << curexists << " wrote " << w << std::endl; + string s = "snap" + stringify(i); + ASSERT_EQ(0, image.snap_create(s.c_str())); + wrote.push_back(w); + exists.push_back(curexists); + snap.push_back(s); + } + + for (int h=0; h<n-1; h++) { + for (int i=0; i<n-h-1; i++) { + for (int j=(h==0 ? i+1 : n-1); j<n; j++) { + interval_set<uint64_t> diff, actual, uex; + for (int k=i+1; k<=j; k++) + diff.union_of(wrote[k]); + cout << "from " << i << " to " + << (h != 0 ? string("HEAD") : stringify(j)) << " diff " + << round_diff_interval(diff, object_size) << std::endl; + + // limit to extents that exists both at the beginning and at the end + uex.union_of(exists[i], exists[j]); + diff.intersection_of(uex); + diff = round_diff_interval(diff, object_size); + cout << " limited diff " << diff << std::endl; + + ASSERT_EQ(0, image.snap_set(h==0 ? snap[j].c_str() : NULL)); + ASSERT_EQ(0, image.diff_iterate2(snap[i].c_str(), 0, size, true, + this->whole_object, iterate_cb, + (void *)&actual)); + cout << " actual was " << actual << std::endl; + if (!diff.subset_of(actual)) { + interval_set<uint64_t> i; + i.intersection_of(diff, actual); + interval_set<uint64_t> l = diff; + l.subtract(i); + cout << " ... diff - (actual*diff) = " << l << std::endl; + } + ASSERT_TRUE(diff.subset_of(actual)); + } + } + ASSERT_EQ(0, image.snap_set(NULL)); + ASSERT_EQ(0, image.snap_remove(snap[n-h-1].c_str())); + } + + ASSERT_PASSED(this->validate_object_map, image); +} + +TYPED_TEST(DiffIterateTest, DiffIterateRegression6926) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = this->get_temp_image_name(); + uint64_t size = 20 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint64_t object_size = 0; + if (this->whole_object) { + object_size = 1 << order; + } + vector<diff_extent> extents; + ceph::bufferlist bl; + + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, (void *) &extents)); + ASSERT_EQ(0u, extents.size()); + + ASSERT_EQ(0, image.snap_create("snap1")); + char data[256]; + memset(data, 1, sizeof(data)); + bl.append(data, 256); + ASSERT_EQ(256, image.write(0, 256, bl)); + + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, (void *) &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + + ASSERT_EQ(0, image.snap_set("snap1")); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, (void *) &extents)); + ASSERT_EQ(static_cast<size_t>(0), extents.size()); +} + +TYPED_TEST(DiffIterateTest, DiffIterateIgnoreParent) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); + + bool skip_discard = this->is_skip_partial_discard_enabled(); + + librbd::RBD rbd; + librbd::Image image; + std::string name = this->get_temp_image_name(); + uint64_t size = 20 << 20; + int order = 0; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint64_t object_size = 0; + if (this->whole_object) { + object_size = 1 << order; + } + + bufferlist bl; + bl.append(buffer::create(size)); + bl.zero(); + interval_set<uint64_t> one; + one.insert(0, size); + ASSERT_EQ((int)size, image.write(0, size, bl)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + + std::string clone_name = this->get_temp_image_name(); + ASSERT_EQ(0, rbd.clone(ioctx, name.c_str(), "one", ioctx, clone_name.c_str(), + RBD_FEATURE_LAYERING, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, clone_name.c_str(), NULL)); + + interval_set<uint64_t> exists; + interval_set<uint64_t> two; + scribble(image, 10, 102400, skip_discard, &exists, &two); + two = round_diff_interval(two, object_size); + cout << " wrote " << two << " to clone" << std::endl; + + interval_set<uint64_t> diff; + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, false, this->whole_object, + iterate_cb, (void *)&diff)); + cout << " diff was " << diff << std::endl; + if (!this->whole_object) { + ASSERT_FALSE(one.subset_of(diff)); + } + ASSERT_TRUE(two.subset_of(diff)); +} + +TYPED_TEST(DiffIterateTest, DiffIterateCallbackError) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); + + bool skip_discard = this->is_skip_partial_discard_enabled(); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = this->get_temp_image_name(); + uint64_t size = 20 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + interval_set<uint64_t> exists; + interval_set<uint64_t> one; + scribble(image, 10, 102400, skip_discard, &exists, &one); + cout << " wrote " << one << std::endl; + + interval_set<uint64_t> diff; + ASSERT_EQ(-EINVAL, image.diff_iterate2(NULL, 0, size, true, + this->whole_object, + iterate_error_cb, NULL)); + } + ioctx.close(); +} + +TYPED_TEST(DiffIterateTest, DiffIterateParentDiscard) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); + + bool skip_discard = this->is_skip_partial_discard_enabled(); + + librbd::RBD rbd; + librbd::Image image; + std::string name = this->get_temp_image_name(); + uint64_t size = 20 << 20; + int order = 0; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint64_t object_size = 0; + if (this->whole_object) { + object_size = 1 << order; + } + + interval_set<uint64_t> exists; + interval_set<uint64_t> one; + scribble(image, 10, 102400, skip_discard, &exists, &one); + ASSERT_EQ(0, image.snap_create("one")); + + ASSERT_EQ(1 << order, image.discard(0, 1 << order)); + ASSERT_EQ(0, image.snap_create("two")); + ASSERT_EQ(0, image.snap_protect("two")); + exists.clear(); + one.clear(); + + std::string clone_name = this->get_temp_image_name(); + ASSERT_EQ(0, rbd.clone(ioctx, name.c_str(), "two", ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, clone_name.c_str(), NULL)); + + interval_set<uint64_t> two; + scribble(image, 10, 102400, skip_discard, &exists, &two); + two = round_diff_interval(two, object_size); + + interval_set<uint64_t> diff; + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + iterate_cb, (void *)&diff)); + ASSERT_TRUE(two.subset_of(diff)); +} + +TEST_F(TestLibRBD, ZeroLengthWrite) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + char read_data[1]; + ASSERT_EQ(0, rbd_write(image, 0, 0, NULL)); + ASSERT_EQ(1, rbd_read(image, 0, 1, read_data)); + ASSERT_EQ('\0', read_data[0]); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + + +TEST_F(TestLibRBD, ZeroLengthDiscard) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + const char data[] = "blah"; + char read_data[sizeof(data)]; + ASSERT_EQ((int)strlen(data), rbd_write(image, 0, strlen(data), data)); + ASSERT_EQ(0, rbd_discard(image, 0, 0)); + ASSERT_EQ((int)strlen(data), rbd_read(image, 0, strlen(data), read_data)); + ASSERT_EQ(0, memcmp(data, read_data, strlen(data))); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, ZeroLengthRead) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + char read_data[1]; + ASSERT_EQ(0, rbd_read(image, 0, 0, read_data)); + + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, LargeCacheRead) +{ + std::string config_value; + ASSERT_EQ(0, _rados.conf_get("rbd_cache", config_value)); + if (config_value == "false") { + std::cout << "SKIPPING due to disabled cache" << std::endl; + return; + } + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + uint32_t new_cache_size = 1 << 20; + std::string orig_cache_size; + ASSERT_EQ(0, _rados.conf_get("rbd_cache_size", orig_cache_size)); + ASSERT_EQ(0, _rados.conf_set("rbd_cache_size", + stringify(new_cache_size).c_str())); + ASSERT_EQ(0, _rados.conf_get("rbd_cache_size", config_value)); + ASSERT_EQ(stringify(new_cache_size), config_value); + BOOST_SCOPE_EXIT( (orig_cache_size) ) { + ASSERT_EQ(0, _rados.conf_set("rbd_cache_size", orig_cache_size.c_str())); + } BOOST_SCOPE_EXIT_END; + + rbd_image_t image; + int order = 21; + std::string name = get_temp_image_name(); + uint64_t size = 1 << order; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + std::string buffer(1 << order, '1'); + + ASSERT_EQ(static_cast<ssize_t>(buffer.size()), + rbd_write(image, 0, buffer.size(), buffer.c_str())); + + ASSERT_EQ(0, rbd_invalidate_cache(image)); + + ASSERT_EQ(static_cast<ssize_t>(buffer.size()), + rbd_read(image, 0, buffer.size(), &buffer[0])); + + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, TestPendingAio) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + bool old_format; + uint64_t features; + rbd_image_t image; + int order = 0; + + ASSERT_EQ(0, get_features(&old_format, &features)); + ASSERT_FALSE(old_format); + + std::string name = get_temp_image_name(); + + uint64_t size = 4 << 20; + ASSERT_EQ(0, create_image_full(ioctx, name.c_str(), size, &order, + false, features)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + char test_data[TEST_IO_SIZE]; + for (size_t i = 0; i < TEST_IO_SIZE; ++i) { + test_data[i] = (char) (rand() % (126 - 33) + 33); + } + + size_t num_aios = 256; + rbd_completion_t comps[num_aios]; + for (size_t i = 0; i < num_aios; ++i) { + ASSERT_EQ(0, rbd_aio_create_completion(NULL, NULL, &comps[i])); + uint64_t offset = rand() % (size - TEST_IO_SIZE); + ASSERT_EQ(0, rbd_aio_write(image, offset, TEST_IO_SIZE, test_data, + comps[i])); + } + for (size_t i = 0; i < num_aios; ++i) { + ASSERT_EQ(0, rbd_aio_wait_for_complete(comps[i])); + rbd_aio_release(comps[i]); + } + ASSERT_EQ(0, rbd_invalidate_cache(image)); + + for (size_t i = 0; i < num_aios; ++i) { + ASSERT_EQ(0, rbd_aio_create_completion(NULL, NULL, &comps[i])); + uint64_t offset = rand() % (size - TEST_IO_SIZE); + ASSERT_LE(0, rbd_aio_read(image, offset, TEST_IO_SIZE, test_data, + comps[i])); + } + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + for (size_t i = 0; i < num_aios; ++i) { + ASSERT_EQ(1, rbd_aio_is_complete(comps[i])); + rbd_aio_release(comps[i]); + } + + rados_ioctx_destroy(ioctx); +} + +void compare_and_write_copyup(librados::IoCtx &ioctx, bool deep_copyup, + bool *passed) +{ + librbd::RBD rbd; + std::string parent_name = TestLibRBD::get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, parent_name.c_str(), size, &order)); + + librbd::Image parent_image; + ASSERT_EQ(0, rbd.open(ioctx, parent_image, parent_name.c_str(), NULL)); + + bufferlist bl; + bl.append(std::string(4096, '1')); + ASSERT_EQ((ssize_t)bl.length(), parent_image.write(0, bl.length(), bl)); + + ASSERT_EQ(0, parent_image.snap_create("snap1")); + ASSERT_EQ(0, parent_image.snap_protect("snap1")); + + uint64_t features; + ASSERT_EQ(0, parent_image.features(&features)); + + std::string clone_name = TestLibRBD::get_temp_image_name(); + EXPECT_EQ(0, rbd.clone(ioctx, parent_name.c_str(), "snap1", ioctx, + clone_name.c_str(), features, &order)); + + librbd::Image clone_image; + ASSERT_EQ(0, rbd.open(ioctx, clone_image, clone_name.c_str(), NULL)); + if (deep_copyup) { + ASSERT_EQ(0, clone_image.snap_create("snap1")); + } + + bufferlist cmp_bl; + cmp_bl.append(std::string(96, '1')); + bufferlist write_bl; + write_bl.append(std::string(512, '2')); + uint64_t mismatch_off; + ASSERT_EQ((ssize_t)write_bl.length(), + clone_image.compare_and_write(512, write_bl.length(), cmp_bl, + write_bl, &mismatch_off, 0)); + + bufferlist read_bl; + ASSERT_EQ(4096, clone_image.read(0, 4096, read_bl)); + + bufferlist expected_bl; + expected_bl.append(std::string(512, '1')); + expected_bl.append(std::string(512, '2')); + expected_bl.append(std::string(3072, '1')); + ASSERT_TRUE(expected_bl.contents_equal(read_bl)); + *passed = true; +} + +TEST_F(TestLibRBD, CompareAndWriteCopyup) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + ASSERT_PASSED(compare_and_write_copyup, ioctx, false); + ASSERT_PASSED(compare_and_write_copyup, ioctx, true); +} + +void compare_and_write_copyup_mismatch(librados::IoCtx &ioctx, + bool deep_copyup, bool *passed) +{ + librbd::RBD rbd; + std::string parent_name = TestLibRBD::get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, parent_name.c_str(), size, &order)); + + librbd::Image parent_image; + ASSERT_EQ(0, rbd.open(ioctx, parent_image, parent_name.c_str(), NULL)); + + bufferlist bl; + bl.append(std::string(4096, '1')); + ASSERT_EQ((ssize_t)bl.length(), parent_image.write(0, bl.length(), bl)); + + ASSERT_EQ(0, parent_image.snap_create("snap1")); + ASSERT_EQ(0, parent_image.snap_protect("snap1")); + + uint64_t features; + ASSERT_EQ(0, parent_image.features(&features)); + + std::string clone_name = TestLibRBD::get_temp_image_name(); + EXPECT_EQ(0, rbd.clone(ioctx, parent_name.c_str(), "snap1", ioctx, + clone_name.c_str(), features, &order)); + + librbd::Image clone_image; + ASSERT_EQ(0, rbd.open(ioctx, clone_image, clone_name.c_str(), NULL)); + if (deep_copyup) { + ASSERT_EQ(0, clone_image.snap_create("snap1")); + } + + bufferlist cmp_bl; + cmp_bl.append(std::string(48, '1')); + cmp_bl.append(std::string(48, '3')); + bufferlist write_bl; + write_bl.append(std::string(512, '2')); + uint64_t mismatch_off; + ASSERT_EQ(-EILSEQ, + clone_image.compare_and_write(512, write_bl.length(), cmp_bl, + write_bl, &mismatch_off, 0)); + ASSERT_EQ(48U, mismatch_off); + + bufferlist read_bl; + ASSERT_EQ(4096, clone_image.read(0, 4096, read_bl)); + + ASSERT_TRUE(bl.contents_equal(read_bl)); + *passed = true; +} + +TEST_F(TestLibRBD, CompareAndWriteCopyupMismatch) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + ASSERT_PASSED(compare_and_write_copyup_mismatch, ioctx, false); + ASSERT_PASSED(compare_and_write_copyup_mismatch, ioctx, true); +} + +TEST_F(TestLibRBD, Flatten) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string parent_name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, parent_name.c_str(), size, &order)); + + librbd::Image parent_image; + ASSERT_EQ(0, rbd.open(ioctx, parent_image, parent_name.c_str(), NULL)); + + bufferlist bl; + bl.append(std::string(4096, '1')); + ASSERT_EQ((ssize_t)bl.length(), parent_image.write(0, bl.length(), bl)); + + ASSERT_EQ(0, parent_image.snap_create("snap1")); + ASSERT_EQ(0, parent_image.snap_protect("snap1")); + + uint64_t features; + ASSERT_EQ(0, parent_image.features(&features)); + + std::string clone_name = get_temp_image_name(); + EXPECT_EQ(0, rbd.clone(ioctx, parent_name.c_str(), "snap1", ioctx, + clone_name.c_str(), features, &order)); + + librbd::Image clone_image; + ASSERT_EQ(0, rbd.open(ioctx, clone_image, clone_name.c_str(), NULL)); + ASSERT_EQ(0, clone_image.flatten()); + + librbd::RBD::AioCompletion *read_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + bufferlist read_bl; + clone_image.aio_read(0, bl.length(), read_bl, read_comp); + ASSERT_EQ(0, read_comp->wait_for_complete()); + ASSERT_EQ((ssize_t)bl.length(), read_comp->get_return_value()); + read_comp->release(); + ASSERT_TRUE(bl.contents_equal(read_bl)); + + ASSERT_PASSED(validate_object_map, clone_image); +} + +TEST_F(TestLibRBD, Sparsify) +{ + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + BOOST_SCOPE_EXIT_ALL(&ioctx) { + rados_ioctx_destroy(ioctx); + }; + + const size_t CHUNK_SIZE = 4096 * 2; + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = CHUNK_SIZE * 1024; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + BOOST_SCOPE_EXIT_ALL(&image) { + rbd_close(image); + }; + + char test_data[4 * CHUNK_SIZE + 1]; + for (size_t i = 0; i < 4 ; ++i) { + for (size_t j = 0; j < CHUNK_SIZE; j++) { + if (i % 2) { + test_data[i * CHUNK_SIZE + j] = (char)(rand() % (126 - 33) + 33); + } else { + test_data[i * CHUNK_SIZE + j] = '\0'; + } + } + } + test_data[4 * CHUNK_SIZE] = '\0'; + + ASSERT_PASSED(write_test_data, image, test_data, 0, 4 * CHUNK_SIZE, 0); + ASSERT_EQ(0, rbd_flush(image)); + + ASSERT_EQ(-EINVAL, rbd_sparsify(image, 16)); + ASSERT_EQ(-EINVAL, rbd_sparsify(image, 1 << (order + 1))); + ASSERT_EQ(-EINVAL, rbd_sparsify(image, 4096 + 1)); + ASSERT_EQ(0, rbd_sparsify(image, 4096)); + + ASSERT_PASSED(read_test_data, image, test_data, 0, 4 * CHUNK_SIZE, 0); +} + +TEST_F(TestLibRBD, SparsifyPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 12 * 1024 * 1024; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + + bufferlist bl; + bl.append(std::string(4096, '\0')); + bl.append(std::string(4096, '1')); + bl.append(std::string(4096, '\0')); + ASSERT_EQ((ssize_t)bl.length(), image.write(0, bl.length(), bl)); + ASSERT_EQ(0, image.flush()); + + ASSERT_EQ(-EINVAL, image.sparsify(16)); + ASSERT_EQ(-EINVAL, image.sparsify(1 << (order + 1))); + ASSERT_EQ(-EINVAL, image.sparsify(4096 + 1)); + ASSERT_EQ(0, image.sparsify(4096)); + + bufferlist read_bl; + ASSERT_EQ((ssize_t)bl.length(), image.read(0, bl.length(), read_bl)); + ASSERT_TRUE(bl.contents_equal(read_bl)); + + ASSERT_PASSED(validate_object_map, image); +} + +TEST_F(TestLibRBD, SnapshotLimit) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + uint64_t limit; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_snap_get_limit(image, &limit)); + ASSERT_EQ(UINT64_MAX, limit); + ASSERT_EQ(0, rbd_snap_set_limit(image, 2)); + ASSERT_EQ(0, rbd_snap_get_limit(image, &limit)); + ASSERT_EQ(2U, limit); + + ASSERT_EQ(0, rbd_snap_create(image, "snap1")); + ASSERT_EQ(-ERANGE, rbd_snap_set_limit(image, 0)); + ASSERT_EQ(0, rbd_snap_create(image, "snap2")); + ASSERT_EQ(-EDQUOT, rbd_snap_create(image, "snap3")); + ASSERT_EQ(0, rbd_snap_set_limit(image, UINT64_MAX)); + ASSERT_EQ(0, rbd_snap_create(image, "snap3")); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + + +TEST_F(TestLibRBD, SnapshotLimitPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + uint64_t limit; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + ASSERT_EQ(0, image.snap_get_limit(&limit)); + ASSERT_EQ(UINT64_MAX, limit); + ASSERT_EQ(0, image.snap_set_limit(2)); + ASSERT_EQ(0, image.snap_get_limit(&limit)); + ASSERT_EQ(2U, limit); + + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ(-ERANGE, image.snap_set_limit(0)); + ASSERT_EQ(0, image.snap_create("snap2")); + ASSERT_EQ(-EDQUOT, image.snap_create("snap3")); + ASSERT_EQ(0, image.snap_set_limit(UINT64_MAX)); + ASSERT_EQ(0, image.snap_create("snap3")); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, RebuildObjectMapViaLockOwner) +{ + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_OBJECT_MAP); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + std::string object_map_oid; + { + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string image_id; + ASSERT_EQ(0, get_image_id(image, &image_id)); + object_map_oid = RBD_OBJECT_MAP_PREFIX + image_id; + } + + // corrupt the object map + bufferlist bl; + bl.append("foo"); + ASSERT_EQ(0, ioctx.write(object_map_oid, bl, bl.length(), 0)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bool lock_owner; + bl.clear(); + ASSERT_EQ(0, image1.write(0, 0, bl)); + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + uint64_t flags; + ASSERT_EQ(0, image1.get_flags(&flags)); + ASSERT_TRUE((flags & RBD_FLAG_OBJECT_MAP_INVALID) != 0); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + PrintProgress prog_ctx; + ASSERT_EQ(0, image2.rebuild_object_map(prog_ctx)); + ASSERT_PASSED(validate_object_map, image1); + ASSERT_PASSED(validate_object_map, image2); +} + +TEST_F(TestLibRBD, RenameViaLockOwner) +{ + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bufferlist bl; + ASSERT_EQ(0, image1.write(0, 0, bl)); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + std::string new_name = get_temp_image_name(); + ASSERT_EQ(0, rbd.rename(ioctx, name.c_str(), new_name.c_str())); + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, new_name.c_str(), NULL)); +} + +TEST_F(TestLibRBD, SnapCreateViaLockOwner) +{ + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + // switch to writeback cache + ASSERT_EQ(0, image1.flush()); + + bufferlist bl; + bl.append(std::string(4096, '1')); + ASSERT_EQ((ssize_t)bl.length(), image1.write(0, bl.length(), bl)); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(0, image2.snap_create("snap1")); + bool exists; + ASSERT_EQ(0, image1.snap_exists2("snap1", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(0, image2.snap_exists2("snap1", &exists)); + ASSERT_TRUE(exists); + + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); +} + +TEST_F(TestLibRBD, SnapRemoveViaLockOwner) +{ + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bufferlist bl; + ASSERT_EQ(0, image1.write(0, 0, bl)); + ASSERT_EQ(0, image1.snap_create("snap1")); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(0, image2.snap_remove("snap1")); + bool exists; + ASSERT_EQ(0, image1.snap_exists2("snap1", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, image2.snap_exists2("snap1", &exists)); + ASSERT_FALSE(exists); + + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); +} + +TEST_F(TestLibRBD, EnableJournalingViaLockOwner) +{ + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bufferlist bl; + ASSERT_EQ(0, image1.write(0, 0, bl)); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + ASSERT_EQ(0, image2.update_features(RBD_FEATURE_JOURNALING, false)); + + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(0, image2.update_features(RBD_FEATURE_JOURNALING, true)); + + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); +} + +TEST_F(TestLibRBD, SnapRemove2) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bufferlist bl; + ASSERT_EQ(0, image1.write(0, 0, bl)); + ASSERT_EQ(0, image1.snap_create("snap1")); + bool exists; + ASSERT_EQ(0, image1.snap_exists2("snap1", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(0, image1.snap_protect("snap1")); + bool is_protected; + ASSERT_EQ(0, image1.snap_is_protected("snap1", &is_protected)); + ASSERT_TRUE(is_protected); + + uint64_t features; + ASSERT_EQ(0, image1.features(&features)); + + std::string child_name = get_temp_image_name(); + EXPECT_EQ(0, rbd.clone(ioctx, name.c_str(), "snap1", ioctx, + child_name.c_str(), features, &order)); + + ASSERT_EQ(0, image1.snap_exists2("snap1", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(0, image1.snap_is_protected("snap1", &is_protected)); + ASSERT_TRUE(is_protected); + + ASSERT_EQ(-EBUSY, image1.snap_remove("snap1")); + PrintProgress pp; + ASSERT_EQ(0, image1.snap_remove2("snap1", RBD_SNAP_REMOVE_FORCE, pp)); + ASSERT_EQ(0, image1.snap_exists2("snap1", &exists)); + ASSERT_FALSE(exists); +} + +TEST_F(TestLibRBD, SnapRenameViaLockOwner) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_EXCLUSIVE_LOCK); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bufferlist bl; + ASSERT_EQ(0, image1.write(0, 0, bl)); + ASSERT_EQ(0, image1.snap_create("snap1")); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(0, image2.snap_rename("snap1", "snap1-rename")); + bool exists; + ASSERT_EQ(0, image1.snap_exists2("snap1-rename", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(0, image2.snap_exists2("snap1-rename", &exists)); + ASSERT_TRUE(exists); + + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); +} + +TEST_F(TestLibRBD, SnapProtectViaLockOwner) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_EXCLUSIVE_LOCK); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bufferlist bl; + ASSERT_EQ(0, image1.write(0, 0, bl)); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + ASSERT_EQ(0, image1.snap_create("snap1")); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(0, image2.snap_protect("snap1")); + bool is_protected; + ASSERT_EQ(0, image2.snap_is_protected("snap1", &is_protected)); + ASSERT_TRUE(is_protected); + ASSERT_EQ(0, image1.snap_is_protected("snap1", &is_protected)); + ASSERT_TRUE(is_protected); + + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); +} + +TEST_F(TestLibRBD, SnapUnprotectViaLockOwner) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_EXCLUSIVE_LOCK); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bufferlist bl; + ASSERT_EQ(0, image1.write(0, 0, bl)); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + ASSERT_EQ(0, image1.snap_create("snap1")); + ASSERT_EQ(0, image1.snap_protect("snap1")); + bool is_protected; + ASSERT_EQ(0, image1.snap_is_protected("snap1", &is_protected)); + ASSERT_TRUE(is_protected); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(0, image2.snap_unprotect("snap1")); + ASSERT_EQ(0, image2.snap_is_protected("snap1", &is_protected)); + ASSERT_FALSE(is_protected); + ASSERT_EQ(0, image1.snap_is_protected("snap1", &is_protected)); + ASSERT_FALSE(is_protected); + + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); +} + +TEST_F(TestLibRBD, FlattenViaLockOwner) +{ + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string parent_name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, parent_name.c_str(), size, &order)); + + librbd::Image parent_image; + ASSERT_EQ(0, rbd.open(ioctx, parent_image, parent_name.c_str(), NULL)); + ASSERT_EQ(0, parent_image.snap_create("snap1")); + ASSERT_EQ(0, parent_image.snap_protect("snap1")); + + uint64_t features; + ASSERT_EQ(0, parent_image.features(&features)); + + std::string name = get_temp_image_name(); + EXPECT_EQ(0, rbd.clone(ioctx, parent_name.c_str(), "snap1", ioctx, + name.c_str(), features, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bufferlist bl; + ASSERT_EQ(0, image1.write(0, 0, bl)); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(0, image2.flatten()); + + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + ASSERT_PASSED(validate_object_map, image1); +} + +TEST_F(TestLibRBD, ResizeViaLockOwner) +{ + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bufferlist bl; + ASSERT_EQ(0, image1.write(0, 0, bl)); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(0, image2.resize(0)); + + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + ASSERT_PASSED(validate_object_map, image1); +} + +TEST_F(TestLibRBD, SparsifyViaLockOwner) +{ + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bufferlist bl; + ASSERT_EQ(0, image1.write(0, 0, bl)); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(0, image2.sparsify(4096)); + + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + ASSERT_PASSED(validate_object_map, image1); +} + +TEST_F(TestLibRBD, ObjectMapConsistentSnap) +{ + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 1 << 20; + int order = 12; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + int num_snaps = 10; + for (int i = 0; i < num_snaps; ++i) { + std::string snap_name = "snap" + stringify(i); + ASSERT_EQ(0, image1.snap_create(snap_name.c_str())); + } + + + thread writer([&image1](){ + librbd::image_info_t info; + int r = image1.stat(info, sizeof(info)); + ceph_assert(r == 0); + bufferlist bl; + bl.append("foo"); + for (unsigned i = 0; i < info.num_objs; ++i) { + r = image1.write((1 << info.order) * i, bl.length(), bl); + ceph_assert(r == (int) bl.length()); + } + }); + writer.join(); + + for (int i = 0; i < num_snaps; ++i) { + std::string snap_name = "snap" + stringify(i); + ASSERT_EQ(0, image1.snap_set(snap_name.c_str())); + ASSERT_PASSED(validate_object_map, image1); + } + + ASSERT_EQ(0, image1.snap_set(NULL)); + ASSERT_PASSED(validate_object_map, image1); +} + +void memset_rand(char *buf, size_t len) { + for (size_t i = 0; i < len; ++i) { + buf[i] = (char) (rand() % (126 - 33) + 33); + } +} + +TEST_F(TestLibRBD, Metadata) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + + rbd_image_t image; + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + rbd_image_t image1; + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image1, NULL)); + + char keys[1024]; + char vals[1024]; + size_t keys_len = sizeof(keys); + size_t vals_len = sizeof(vals); + + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + + ASSERT_EQ(0, rbd_metadata_list(image, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(0U, keys_len); + ASSERT_EQ(0U, vals_len); + + char value[1024]; + size_t value_len = sizeof(value); + memset_rand(value, value_len); + + ASSERT_EQ(0, rbd_metadata_set(image1, "key1", "value1")); + ASSERT_EQ(0, rbd_metadata_set(image1, "key2", "value2")); + ASSERT_EQ(0, rbd_metadata_get(image1, "key1", value, &value_len)); + ASSERT_STREQ(value, "value1"); + value_len = 1; + ASSERT_EQ(-ERANGE, rbd_metadata_get(image1, "key1", value, &value_len)); + ASSERT_EQ(value_len, strlen("value1") + 1); + + ASSERT_EQ(-ERANGE, rbd_metadata_list(image1, "", 0, keys, &keys_len, vals, + &vals_len)); + keys_len = sizeof(keys); + vals_len = sizeof(vals); + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_metadata_list(image1, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1 + strlen("key2") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1 + strlen("value2") + 1); + ASSERT_STREQ(keys, "key1"); + ASSERT_STREQ(keys + strlen(keys) + 1, "key2"); + ASSERT_STREQ(vals, "value1"); + ASSERT_STREQ(vals + strlen(vals) + 1, "value2"); + + ASSERT_EQ(0, rbd_metadata_remove(image1, "key1")); + ASSERT_EQ(-ENOENT, rbd_metadata_remove(image1, "key3")); + value_len = sizeof(value); + ASSERT_EQ(-ENOENT, rbd_metadata_get(image1, "key3", value, &value_len)); + ASSERT_EQ(0, rbd_metadata_list(image1, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key2") + 1); + ASSERT_EQ(vals_len, strlen("value2") + 1); + ASSERT_STREQ(keys, "key2"); + ASSERT_STREQ(vals, "value2"); + + // test config setting + ASSERT_EQ(0, rbd_metadata_set(image1, "conf_rbd_cache", "false")); + ASSERT_EQ(-EINVAL, rbd_metadata_set(image1, "conf_rbd_cache", "INVALID_VAL")); + ASSERT_EQ(0, rbd_metadata_remove(image1, "conf_rbd_cache")); + + // test metadata with snapshot adding + ASSERT_EQ(0, rbd_snap_create(image1, "snap1")); + ASSERT_EQ(0, rbd_snap_protect(image1, "snap1")); + ASSERT_EQ(0, rbd_snap_set(image1, "snap1")); + + ASSERT_EQ(0, rbd_metadata_set(image1, "key1", "value1")); + ASSERT_EQ(0, rbd_metadata_set(image1, "key3", "value3")); + + keys_len = sizeof(keys); + vals_len = sizeof(vals); + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_metadata_list(image1, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, + strlen("key1") + 1 + strlen("key2") + 1 + strlen("key3") + 1); + ASSERT_EQ(vals_len, + strlen("value1") + 1 + strlen("value2") + 1 + strlen("value3") + 1); + ASSERT_STREQ(keys, "key1"); + ASSERT_STREQ(keys + strlen("key1") + 1, "key2"); + ASSERT_STREQ(keys + strlen("key1") + 1 + strlen("key2") + 1, "key3"); + ASSERT_STREQ(vals, "value1"); + ASSERT_STREQ(vals + strlen("value1") + 1, "value2"); + ASSERT_STREQ(vals + strlen("value1") + 1 + strlen("value2") + 1, "value3"); + + ASSERT_EQ(0, rbd_snap_set(image1, NULL)); + keys_len = sizeof(keys); + vals_len = sizeof(vals); + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_metadata_list(image1, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, + strlen("key1") + 1 + strlen("key2") + 1 + strlen("key3") + 1); + ASSERT_EQ(vals_len, + strlen("value1") + 1 + strlen("value2") + 1 + strlen("value3") + 1); + ASSERT_STREQ(keys, "key1"); + ASSERT_STREQ(keys + strlen("key1") + 1, "key2"); + ASSERT_STREQ(keys + strlen("key1") + 1 + strlen("key2") + 1, "key3"); + ASSERT_STREQ(vals, "value1"); + ASSERT_STREQ(vals + strlen("value1") + 1, "value2"); + ASSERT_STREQ(vals + strlen("value1") + 1 + strlen("value2") + 1, "value3"); + + // test metadata with cloning + uint64_t features; + ASSERT_EQ(0, rbd_get_features(image1, &features)); + + string cname = get_temp_image_name(); + EXPECT_EQ(0, rbd_clone(ioctx, name.c_str(), "snap1", ioctx, + cname.c_str(), features, &order)); + rbd_image_t image2; + ASSERT_EQ(0, rbd_open(ioctx, cname.c_str(), &image2, NULL)); + ASSERT_EQ(0, rbd_metadata_set(image2, "key4", "value4")); + + keys_len = sizeof(keys); + vals_len = sizeof(vals); + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_metadata_list(image2, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1 + strlen("key2") + 1 + strlen("key3") + + 1 + strlen("key4") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1 + strlen("value2") + 1 + + strlen("value3") + 1 + strlen("value4") + 1); + ASSERT_STREQ(keys + strlen("key1") + 1 + strlen("key2") + 1 + strlen("key3") + + 1, "key4"); + ASSERT_STREQ(vals + strlen("value1") + 1 + strlen("value2") + 1 + + strlen("value3") + 1, "value4"); + + ASSERT_EQ(0, rbd_metadata_list(image1, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, + strlen("key1") + 1 + strlen("key2") + 1 + strlen("key3") + 1); + ASSERT_EQ(vals_len, + strlen("value1") + 1 + strlen("value2") + 1 + strlen("value3") + 1); + ASSERT_EQ(-ENOENT, rbd_metadata_get(image1, "key4", value, &value_len)); + + // test short buffer cases + keys_len = strlen("key1") + 1; + vals_len = strlen("value1") + 1; + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_metadata_list(image2, "", 1, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1); + ASSERT_STREQ(keys, "key1"); + ASSERT_STREQ(vals, "value1"); + + ASSERT_EQ(-ERANGE, rbd_metadata_list(image2, "", 2, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1 + strlen("key2") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1 + strlen("value2") + 1); + + ASSERT_EQ(-ERANGE, rbd_metadata_list(image2, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1 + strlen("key2") + 1 + strlen("key3") + + 1 + strlen("key4") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1 + strlen("value2") + 1 + + strlen("value3") + 1 + strlen("value4") + 1); + + // test `start` param + keys_len = sizeof(keys); + vals_len = sizeof(vals); + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_metadata_list(image2, "key2", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key3") + 1 + strlen("key4") + 1); + ASSERT_EQ(vals_len, strlen("value3") + 1 + strlen("value4") + 1); + ASSERT_STREQ(keys, "key3"); + ASSERT_STREQ(vals, "value3"); + + ASSERT_EQ(0, rbd_close(image)); + ASSERT_EQ(0, rbd_close(image1)); + ASSERT_EQ(0, rbd_close(image2)); + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, MetadataPP) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + uint64_t features; + string value; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + map<string, bufferlist> pairs; + ASSERT_EQ(0, image1.metadata_list("", 0, &pairs)); + ASSERT_TRUE(pairs.empty()); + + ASSERT_EQ(0, image1.metadata_set("key1", "value1")); + ASSERT_EQ(0, image1.metadata_set("key2", "value2")); + ASSERT_EQ(0, image1.metadata_get("key1", &value)); + ASSERT_EQ(0, strcmp("value1", value.c_str())); + ASSERT_EQ(0, image1.metadata_list("", 0, &pairs)); + ASSERT_EQ(2U, pairs.size()); + ASSERT_EQ(0, strncmp("value1", pairs["key1"].c_str(), 6)); + ASSERT_EQ(0, strncmp("value2", pairs["key2"].c_str(), 6)); + + pairs.clear(); + ASSERT_EQ(0, image1.metadata_remove("key1")); + ASSERT_EQ(-ENOENT, image1.metadata_remove("key3")); + ASSERT_TRUE(image1.metadata_get("key3", &value) < 0); + ASSERT_EQ(0, image1.metadata_list("", 0, &pairs)); + ASSERT_EQ(1U, pairs.size()); + ASSERT_EQ(0, strncmp("value2", pairs["key2"].c_str(), 6)); + + // test config setting + ASSERT_EQ(0, image1.metadata_set("conf_rbd_cache", "false")); + ASSERT_EQ(-EINVAL, image1.metadata_set("conf_rbd_cache", "INVALID_VALUE")); + ASSERT_EQ(0, image1.metadata_remove("conf_rbd_cache")); + + // test metadata with snapshot adding + ASSERT_EQ(0, image1.snap_create("snap1")); + ASSERT_EQ(0, image1.snap_protect("snap1")); + ASSERT_EQ(0, image1.snap_set("snap1")); + + pairs.clear(); + ASSERT_EQ(0, image1.metadata_set("key1", "value1")); + ASSERT_EQ(0, image1.metadata_set("key3", "value3")); + ASSERT_EQ(0, image1.metadata_list("", 0, &pairs)); + ASSERT_EQ(3U, pairs.size()); + ASSERT_EQ(0, strncmp("value1", pairs["key1"].c_str(), 6)); + ASSERT_EQ(0, strncmp("value2", pairs["key2"].c_str(), 6)); + ASSERT_EQ(0, strncmp("value3", pairs["key3"].c_str(), 6)); + + ASSERT_EQ(0, image1.snap_set(NULL)); + ASSERT_EQ(0, image1.metadata_list("", 0, &pairs)); + ASSERT_EQ(3U, pairs.size()); + ASSERT_EQ(0, strncmp("value1", pairs["key1"].c_str(), 6)); + ASSERT_EQ(0, strncmp("value2", pairs["key2"].c_str(), 6)); + ASSERT_EQ(0, strncmp("value3", pairs["key3"].c_str(), 6)); + + // test metadata with cloning + string cname = get_temp_image_name(); + librbd::Image image2; + ASSERT_EQ(0, image1.features(&features)); + EXPECT_EQ(0, rbd.clone(ioctx, name.c_str(), "snap1", ioctx, + cname.c_str(), features, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image2, cname.c_str(), NULL)); + ASSERT_EQ(0, image2.metadata_set("key4", "value4")); + pairs.clear(); + ASSERT_EQ(0, image2.metadata_list("", 0, &pairs)); + ASSERT_EQ(4U, pairs.size()); + pairs.clear(); + ASSERT_EQ(0, image1.metadata_list("", 0, &pairs)); + ASSERT_EQ(3U, pairs.size()); + ASSERT_EQ(-ENOENT, image1.metadata_get("key4", &value)); +} + +TEST_F(TestLibRBD, UpdateFeatures) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 1 << 20; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint8_t old_format; + ASSERT_EQ(0, image.old_format(&old_format)); + if (old_format) { + ASSERT_EQ(-EINVAL, image.update_features(RBD_FEATURE_EXCLUSIVE_LOCK, true)); + return; + } + + uint64_t features; + ASSERT_EQ(0, image.features(&features)); + + // must provide a single feature + ASSERT_EQ(-EINVAL, image.update_features(0, true)); + + uint64_t disable_features; + disable_features = features & (RBD_FEATURE_EXCLUSIVE_LOCK | + RBD_FEATURE_OBJECT_MAP | + RBD_FEATURE_FAST_DIFF | + RBD_FEATURE_JOURNALING); + if (disable_features != 0) { + ASSERT_EQ(0, image.update_features(disable_features, false)); + } + + ASSERT_EQ(0, image.features(&features)); + ASSERT_EQ(0U, features & disable_features); + + // cannot enable object map nor journaling w/o exclusive lock + ASSERT_EQ(-EINVAL, image.update_features(RBD_FEATURE_OBJECT_MAP, true)); + ASSERT_EQ(-EINVAL, image.update_features(RBD_FEATURE_JOURNALING, true)); + ASSERT_EQ(0, image.update_features(RBD_FEATURE_EXCLUSIVE_LOCK, true)); + + ASSERT_EQ(0, image.features(&features)); + ASSERT_NE(0U, features & RBD_FEATURE_EXCLUSIVE_LOCK); + + // can enable fast diff w/o object map + ASSERT_EQ(0, image.update_features(RBD_FEATURE_FAST_DIFF, true)); + ASSERT_EQ(-EINVAL, image.update_features(RBD_FEATURE_OBJECT_MAP, true)); + ASSERT_EQ(0, image.features(&features)); + ASSERT_NE(0U, features & RBD_FEATURE_OBJECT_MAP); + + uint64_t expected_flags = RBD_FLAG_OBJECT_MAP_INVALID | + RBD_FLAG_FAST_DIFF_INVALID; + uint64_t flags; + ASSERT_EQ(0, image.get_flags(&flags)); + ASSERT_EQ(expected_flags, flags); + + ASSERT_EQ(0, image.update_features(RBD_FEATURE_OBJECT_MAP, false)); + ASSERT_EQ(0, image.features(&features)); + ASSERT_EQ(0U, features & RBD_FEATURE_OBJECT_MAP); + + // can disable object map w/ fast diff + ASSERT_EQ(0, image.update_features(RBD_FEATURE_OBJECT_MAP, true)); + ASSERT_EQ(0, image.update_features(RBD_FEATURE_OBJECT_MAP, false)); + ASSERT_EQ(-EINVAL, image.update_features(RBD_FEATURE_FAST_DIFF, false)); + ASSERT_EQ(0, image.features(&features)); + ASSERT_EQ(0U, features & RBD_FEATURE_FAST_DIFF); + + ASSERT_EQ(0, image.get_flags(&flags)); + ASSERT_EQ(0U, flags); + + // cannot disable exclusive lock w/ object map + ASSERT_EQ(0, image.update_features(RBD_FEATURE_OBJECT_MAP, true)); + ASSERT_EQ(-EINVAL, image.update_features(RBD_FEATURE_EXCLUSIVE_LOCK, false)); + ASSERT_EQ(0, image.update_features(RBD_FEATURE_OBJECT_MAP, false)); + + // cannot disable exclusive lock w/ journaling + ASSERT_EQ(0, image.update_features(RBD_FEATURE_JOURNALING, true)); + ASSERT_EQ(-EINVAL, image.update_features(RBD_FEATURE_EXCLUSIVE_LOCK, false)); + ASSERT_EQ(0, image.update_features(RBD_FEATURE_JOURNALING, false)); + + ASSERT_EQ(0, image.get_flags(&flags)); + ASSERT_EQ(0U, flags); + + ASSERT_EQ(0, image.update_features(RBD_FEATURE_EXCLUSIVE_LOCK, false)); + + ASSERT_EQ(0, image.features(&features)); + if ((features & RBD_FEATURE_DEEP_FLATTEN) != 0) { + ASSERT_EQ(0, image.update_features(RBD_FEATURE_DEEP_FLATTEN, false)); + } + ASSERT_EQ(-EINVAL, image.update_features(RBD_FEATURE_DEEP_FLATTEN, true)); +} + +TEST_F(TestLibRBD, RebuildObjectMap) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 1 << 20; + int order = 18; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + PrintProgress prog_ctx; + std::string object_map_oid; + bufferlist bl; + bl.append("foo"); + { + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint64_t features; + ASSERT_EQ(0, image.features(&features)); + if ((features & RBD_FEATURE_OBJECT_MAP) == 0) { + ASSERT_EQ(-EINVAL, image.rebuild_object_map(prog_ctx)); + return; + } + + ASSERT_EQ((ssize_t)bl.length(), image.write(0, bl.length(), bl)); + + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ((ssize_t)bl.length(), image.write(1<<order, bl.length(), bl)); + + std::string image_id; + ASSERT_EQ(0, get_image_id(image, &image_id)); + object_map_oid = RBD_OBJECT_MAP_PREFIX + image_id; + } + + // corrupt the object map + ASSERT_EQ(0, ioctx.write(object_map_oid, bl, bl.length(), 0)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bool lock_owner; + bl.clear(); + ASSERT_EQ(0, image1.write(0, 0, bl)); + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + uint64_t flags; + ASSERT_EQ(0, image1.get_flags(&flags)); + ASSERT_TRUE((flags & RBD_FLAG_OBJECT_MAP_INVALID) != 0); + + ASSERT_EQ(0, image1.rebuild_object_map(prog_ctx)); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + bufferlist read_bl; + ASSERT_EQ((ssize_t)bl.length(), image2.read(0, bl.length(), read_bl)); + ASSERT_TRUE(bl.contents_equal(read_bl)); + + read_bl.clear(); + ASSERT_EQ((ssize_t)bl.length(), image2.read(1<<order, bl.length(), read_bl)); + ASSERT_TRUE(bl.contents_equal(read_bl)); + + ASSERT_PASSED(validate_object_map, image1); + ASSERT_PASSED(validate_object_map, image2); +} + +TEST_F(TestLibRBD, RebuildNewObjectMap) +{ + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + std::string name = get_temp_image_name(); + uint64_t size = 1 << 20; + int order = 18; + uint64_t features = RBD_FEATURE_EXCLUSIVE_LOCK; + ASSERT_EQ(0, create_image_full(ioctx, name.c_str(), size, &order, + false, features)); + + rbd_image_t image; + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + ASSERT_EQ(0, rbd_update_features(image, RBD_FEATURE_OBJECT_MAP, true)); + ASSERT_EQ(0, rbd_rebuild_object_map(image, print_progress_percent, NULL)); + + ASSERT_PASSED(validate_object_map, image); + + ASSERT_EQ(0, rbd_close(image)); + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, CheckObjectMap) +{ + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 1 << 20; + int order = 18; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + PrintProgress prog_ctx; + bufferlist bl1; + bufferlist bl2; + bl1.append("foo"); + { + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint64_t features; + ASSERT_EQ(0, image.features(&features)); + + ASSERT_EQ((ssize_t)bl1.length(), image.write(0, bl1.length(), bl1)); + + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ((ssize_t)bl1.length(), image.write(1<<order, bl1.length(), bl1)); + } + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + std::string image_id; + ASSERT_EQ(0, get_image_id(image1, &image_id)); + + std::string object_map_oid = RBD_OBJECT_MAP_PREFIX + image_id; + + ASSERT_LT(0, ioctx.read(object_map_oid, bl2, 1024, 0)); + + bool lock_owner; + ASSERT_EQ((ssize_t)bl1.length(), image1.write(3 * (1 << 18), bl1.length(), bl1)); + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + //reopen image to reread now corrupt object map from disk + image1.close(); + + bl1.clear(); + ASSERT_LT(0, ioctx.read(object_map_oid, bl1, 1024, 0)); + ASSERT_FALSE(bl1.contents_equal(bl2)); + + ASSERT_EQ(0, ioctx.write_full(object_map_oid, bl2)); + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + uint64_t flags; + ASSERT_EQ(0, image1.get_flags(&flags)); + ASSERT_TRUE((flags & RBD_FLAG_OBJECT_MAP_INVALID) == 0); + + ASSERT_EQ(0, image1.check_object_map(prog_ctx)); + + ASSERT_EQ(0, image1.get_flags(&flags)); + ASSERT_TRUE((flags & RBD_FLAG_OBJECT_MAP_INVALID) != 0); +} + +TEST_F(TestLibRBD, BlockingAIO) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + bool skip_discard = is_skip_partial_discard_enabled(); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 1 << 20; + int order = 18; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + std::string non_blocking_aio; + ASSERT_EQ(0, _rados.conf_get("rbd_non_blocking_aio", non_blocking_aio)); + ASSERT_EQ(0, _rados.conf_set("rbd_non_blocking_aio", "0")); + BOOST_SCOPE_EXIT( (non_blocking_aio) ) { + ASSERT_EQ(0, _rados.conf_set("rbd_non_blocking_aio", + non_blocking_aio.c_str())); + } BOOST_SCOPE_EXIT_END; + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + bufferlist bl; + ASSERT_EQ(0, image.write(0, bl.length(), bl)); + + bl.append(std::string(256, '1')); + librbd::RBD::AioCompletion *write_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, image.aio_write(0, bl.length(), bl, write_comp)); + + librbd::RBD::AioCompletion *flush_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, image.aio_flush(flush_comp)); + ASSERT_EQ(0, flush_comp->wait_for_complete()); + ASSERT_EQ(0, flush_comp->get_return_value()); + flush_comp->release(); + + ASSERT_EQ(1, write_comp->is_complete()); + ASSERT_EQ(0, write_comp->get_return_value()); + write_comp->release(); + + librbd::RBD::AioCompletion *discard_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, image.aio_discard(128, 128, discard_comp)); + ASSERT_EQ(0, discard_comp->wait_for_complete()); + discard_comp->release(); + + librbd::RBD::AioCompletion *read_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + bufferlist read_bl; + image.aio_read(0, bl.length(), read_bl, read_comp); + ASSERT_EQ(0, read_comp->wait_for_complete()); + ASSERT_EQ((ssize_t)bl.length(), read_comp->get_return_value()); + read_comp->release(); + + bufferlist expected_bl; + expected_bl.append(std::string(128, '1')); + expected_bl.append(std::string(128, skip_discard ? '1' : '\0')); + ASSERT_TRUE(expected_bl.contents_equal(read_bl)); +} + +TEST_F(TestLibRBD, ExclusiveLockTransition) +{ + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + + uint64_t size = 1 << 18; + int order = 12; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + std::list<librbd::RBD::AioCompletion *> comps; + ceph::bufferlist bl; + bl.append(std::string(1 << order, '1')); + for (size_t object_no = 0; object_no < (size >> 12); ++object_no) { + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, + NULL); + comps.push_back(comp); + if (object_no % 2 == 0) { + ASSERT_EQ(0, image1.aio_write(object_no << order, bl.length(), bl, comp)); + } else { + ASSERT_EQ(0, image2.aio_write(object_no << order, bl.length(), bl, comp)); + } + } + + while (!comps.empty()) { + librbd::RBD::AioCompletion *comp = comps.front(); + comps.pop_front(); + ASSERT_EQ(0, comp->wait_for_complete()); + ASSERT_EQ(1, comp->is_complete()); + comp->release(); + } + + librbd::Image image3; + ASSERT_EQ(0, rbd.open(ioctx, image3, name.c_str(), NULL)); + for (size_t object_no = 0; object_no < (size >> 12); ++object_no) { + bufferlist read_bl; + ASSERT_EQ((ssize_t)bl.length(), image3.read(object_no << order, bl.length(), + read_bl)); + ASSERT_TRUE(bl.contents_equal(read_bl)); + } + + ASSERT_PASSED(validate_object_map, image1); + ASSERT_PASSED(validate_object_map, image2); + ASSERT_PASSED(validate_object_map, image3); +} + +TEST_F(TestLibRBD, ExclusiveLockReadTransition) +{ + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + + uint64_t size = 1 << 18; + int order = 12; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + // journaling should force read ops to acquire the lock + bufferlist read_bl; + ASSERT_EQ(0, image1.read(0, 0, read_bl)); + + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + std::list<librbd::RBD::AioCompletion *> comps; + std::list<bufferlist> read_bls; + for (size_t object_no = 0; object_no < (size >> 12); ++object_no) { + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion(NULL, + NULL); + comps.push_back(comp); + read_bls.emplace_back(); + if (object_no % 2 == 0) { + ASSERT_EQ(0, image1.aio_read(object_no << order, 1 << order, read_bls.back(), comp)); + } else { + ASSERT_EQ(0, image2.aio_read(object_no << order, 1 << order, read_bls.back(), comp)); + } + } + + while (!comps.empty()) { + librbd::RBD::AioCompletion *comp = comps.front(); + comps.pop_front(); + ASSERT_EQ(0, comp->wait_for_complete()); + ASSERT_EQ(1, comp->is_complete()); + comp->release(); + } +} + +TEST_F(TestLibRBD, CacheMayCopyOnWrite) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + + uint64_t size = 1 << 18; + int order = 12; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + + std::string clone_name = this->get_temp_image_name(); + ASSERT_EQ(0, rbd.clone(ioctx, name.c_str(), "one", ioctx, clone_name.c_str(), + RBD_FEATURE_LAYERING, &order)); + + librbd::Image clone; + ASSERT_EQ(0, rbd.open(ioctx, clone, clone_name.c_str(), NULL)); + ASSERT_EQ(0, clone.flush()); + + bufferlist expect_bl; + expect_bl.append(std::string(1024, '\0')); + + // test double read path + bufferlist read_bl; + uint64_t offset = 0; + ASSERT_EQ(1024, clone.read(offset + 2048, 1024, read_bl)); + ASSERT_TRUE(expect_bl.contents_equal(read_bl)); + + bufferlist write_bl; + write_bl.append(std::string(1024, '1')); + ASSERT_EQ(1024, clone.write(offset, write_bl.length(), write_bl)); + + read_bl.clear(); + ASSERT_EQ(1024, clone.read(offset + 2048, 1024, read_bl)); + ASSERT_TRUE(expect_bl.contents_equal(read_bl)); + + // test read retry path + offset = 1 << order; + ASSERT_EQ(1024, clone.write(offset, write_bl.length(), write_bl)); + + read_bl.clear(); + ASSERT_EQ(1024, clone.read(offset + 2048, 1024, read_bl)); + ASSERT_TRUE(expect_bl.contents_equal(read_bl)); +} + +TEST_F(TestLibRBD, FlushEmptyOpsOnExternalSnapshot) { + std::string cache_enabled; + ASSERT_EQ(0, _rados.conf_get("rbd_cache", cache_enabled)); + ASSERT_EQ(0, _rados.conf_set("rbd_cache", "false")); + BOOST_SCOPE_EXIT( (cache_enabled) ) { + ASSERT_EQ(0, _rados.conf_set("rbd_cache", cache_enabled.c_str())); + } BOOST_SCOPE_EXIT_END; + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 1 << 18; + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + ASSERT_EQ(0, image1.snap_create("snap1")); + + librbd::RBD::AioCompletion *read_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + bufferlist read_bl; + image2.aio_read(0, 1024, read_bl, read_comp); + ASSERT_EQ(0, read_comp->wait_for_complete()); + read_comp->release(); +} + +TEST_F(TestLibRBD, TestImageOptions) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + //make create image options + uint64_t features = RBD_FEATURE_LAYERING | RBD_FEATURE_STRIPINGV2 ; + uint64_t order = 0; + uint64_t stripe_unit = IMAGE_STRIPE_UNIT; + uint64_t stripe_count = IMAGE_STRIPE_COUNT; + rbd_image_options_t opts; + rbd_image_options_create(&opts); + + bool is_set; + ASSERT_EQ(-EINVAL, rbd_image_options_is_set(opts, 12345, &is_set)); + ASSERT_EQ(0, rbd_image_options_is_set(opts, RBD_IMAGE_OPTION_FORMAT, + &is_set)); + ASSERT_FALSE(is_set); + + ASSERT_EQ(0, rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FORMAT, + 2)); + ASSERT_EQ(0, rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FEATURES, + features)); + ASSERT_EQ(0, rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_ORDER, + order)); + ASSERT_EQ(0, rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_UNIT, + stripe_unit)); + ASSERT_EQ(0, rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_STRIPE_COUNT, + stripe_count)); + + ASSERT_EQ(0, rbd_image_options_is_set(opts, RBD_IMAGE_OPTION_FORMAT, + &is_set)); + ASSERT_TRUE(is_set); + + std::string parent_name = get_temp_image_name(); + + // make parent + ASSERT_EQ(0, rbd_create4(ioctx, parent_name.c_str(), 4<<20, opts)); + + // check order is returned in opts + ASSERT_EQ(0, rbd_image_options_get_uint64(opts, RBD_IMAGE_OPTION_ORDER, + &order)); + ASSERT_NE((uint64_t)0, order); + + // write some data to parent + rbd_image_t parent; + ASSERT_EQ(0, rbd_open(ioctx, parent_name.c_str(), &parent, NULL)); + char *data = (char *)"testdata"; + ASSERT_EQ((ssize_t)strlen(data), rbd_write(parent, 0, strlen(data), data)); + ASSERT_EQ((ssize_t)strlen(data), rbd_write(parent, 12, strlen(data), data)); + + // create a snapshot, reopen as the parent we're interested in + ASSERT_EQ(0, rbd_snap_create(parent, "parent_snap")); + ASSERT_EQ(0, rbd_close(parent)); + ASSERT_EQ(0, rbd_open(ioctx, parent_name.c_str(), &parent, "parent_snap")); + + // clone + std::string child_name = get_temp_image_name(); + ASSERT_EQ(0, rbd_snap_protect(parent, "parent_snap")); + ASSERT_EQ(0, rbd_clone3(ioctx, parent_name.c_str(), "parent_snap", ioctx, + child_name.c_str(), opts)); + + // copy + std::string copy1_name = get_temp_image_name(); + ASSERT_EQ(0, rbd_copy3(parent, ioctx, copy1_name.c_str(), opts)); + std::string copy2_name = get_temp_image_name(); + ASSERT_EQ(0, rbd_copy_with_progress3(parent, ioctx, copy2_name.c_str(), opts, + print_progress_percent, NULL)); + + ASSERT_EQ(0, rbd_close(parent)); + + rbd_image_options_destroy(opts); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, TestImageOptionsPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + //make create image options + uint64_t features = RBD_FEATURE_LAYERING | RBD_FEATURE_STRIPINGV2 ; + uint64_t order = 0; + uint64_t stripe_unit = IMAGE_STRIPE_UNIT; + uint64_t stripe_count = IMAGE_STRIPE_COUNT; + librbd::ImageOptions opts; + ASSERT_EQ(0, opts.set(RBD_IMAGE_OPTION_FORMAT, static_cast<uint64_t>(2))); + ASSERT_EQ(0, opts.set(RBD_IMAGE_OPTION_FEATURES, features)); + ASSERT_EQ(0, opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + librbd::RBD rbd; + std::string parent_name = get_temp_image_name(); + + // make parent + ASSERT_EQ(0, rbd.create4(ioctx, parent_name.c_str(), 4<<20, opts)); + + // check order is returned in opts + ASSERT_EQ(0, opts.get(RBD_IMAGE_OPTION_ORDER, &order)); + ASSERT_NE((uint64_t)0, order); + + // write some data to parent + librbd::Image parent; + ASSERT_EQ(0, rbd.open(ioctx, parent, parent_name.c_str(), NULL)); + + ssize_t len = 1024; + bufferlist bl; + bl.append(buffer::create(len)); + bl.zero(); + ASSERT_EQ(len, parent.write(0, len, bl)); + ASSERT_EQ(len, parent.write(len, len, bl)); + + // create a snapshot, reopen as the parent we're interested in + ASSERT_EQ(0, parent.snap_create("parent_snap")); + ASSERT_EQ(0, parent.close()); + ASSERT_EQ(0, rbd.open(ioctx, parent, parent_name.c_str(), "parent_snap")); + + // clone + std::string child_name = get_temp_image_name(); + ASSERT_EQ(0, parent.snap_protect("parent_snap")); + ASSERT_EQ(0, rbd.clone3(ioctx, parent_name.c_str(), "parent_snap", ioctx, + child_name.c_str(), opts)); + + // copy + std::string copy1_name = get_temp_image_name(); + ASSERT_EQ(0, parent.copy3(ioctx, copy1_name.c_str(), opts)); + std::string copy2_name = get_temp_image_name(); + PrintProgress pp; + ASSERT_EQ(0, parent.copy_with_progress3(ioctx, copy2_name.c_str(), opts, pp)); + + ASSERT_EQ(0, parent.close()); +} + +TEST_F(TestLibRBD, EventSocketPipe) +{ + EventSocket event_sock; + int pipe_fd[2]; // read and write fd + char buf[32]; + + ASSERT_EQ(0, pipe(pipe_fd)); + + ASSERT_FALSE(event_sock.is_valid()); + + ASSERT_EQ(-EINVAL, event_sock.init(pipe_fd[1], EVENT_SOCKET_TYPE_NONE)); + ASSERT_FALSE(event_sock.is_valid()); + + ASSERT_EQ(-EINVAL, event_sock.init(pipe_fd[1], 44)); + ASSERT_FALSE(event_sock.is_valid()); + +#ifndef HAVE_EVENTFD + ASSERT_EQ(-EINVAL, event_sock.init(pipe_fd[1], EVENT_SOCKET_TYPE_EVENTFD)); + ASSERT_FALSE(event_sock.is_valid()); +#endif + + ASSERT_EQ(0, event_sock.init(pipe_fd[1], EVENT_SOCKET_TYPE_PIPE)); + ASSERT_TRUE(event_sock.is_valid()); + ASSERT_EQ(0, event_sock.notify()); + ASSERT_EQ(1, read(pipe_fd[0], buf, 32)); + ASSERT_EQ('i', buf[0]); + + close(pipe_fd[0]); + close(pipe_fd[1]); +} + +TEST_F(TestLibRBD, EventSocketEventfd) +{ +#ifdef HAVE_EVENTFD + EventSocket event_sock; + int event_fd; + struct pollfd poll_fd; + char buf[32]; + + event_fd = eventfd(0, EFD_NONBLOCK); + ASSERT_NE(-1, event_fd); + + ASSERT_FALSE(event_sock.is_valid()); + + ASSERT_EQ(-EINVAL, event_sock.init(event_fd, EVENT_SOCKET_TYPE_NONE)); + ASSERT_FALSE(event_sock.is_valid()); + + ASSERT_EQ(-EINVAL, event_sock.init(event_fd, 44)); + ASSERT_FALSE(event_sock.is_valid()); + + ASSERT_EQ(0, event_sock.init(event_fd, EVENT_SOCKET_TYPE_EVENTFD)); + ASSERT_TRUE(event_sock.is_valid()); + ASSERT_EQ(0, event_sock.notify()); + + poll_fd.fd = event_fd; + poll_fd.events = POLLIN; + ASSERT_EQ(1, poll(&poll_fd, 1, -1)); + ASSERT_TRUE(poll_fd.revents & POLLIN); + + ASSERT_EQ(static_cast<ssize_t>(sizeof(uint64_t)), read(event_fd, buf, 32)); + ASSERT_EQ(1U, *reinterpret_cast<uint64_t *>(buf)); + + close(event_fd); +#endif +} + +TEST_F(TestLibRBD, ImagePollIO) +{ +#ifdef HAVE_EVENTFD + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int fd = eventfd(0, EFD_NONBLOCK); + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_set_image_notification(image, fd, EVENT_SOCKET_TYPE_EVENTFD)); + + char test_data[TEST_IO_SIZE + 1]; + char zero_data[TEST_IO_SIZE + 1]; + int i; + + for (i = 0; i < TEST_IO_SIZE; ++i) + test_data[i] = (char) (rand() % (126 - 33) + 33); + test_data[TEST_IO_SIZE] = '\0'; + memset(zero_data, 0, sizeof(zero_data)); + + for (i = 0; i < 5; ++i) + ASSERT_PASSED(write_test_data, image, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_write_test_data_and_poll, image, fd, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + for (i = 5; i < 10; ++i) + ASSERT_PASSED(aio_read_test_data_and_poll, image, fd, test_data, TEST_IO_SIZE * i, TEST_IO_SIZE, 0); + + ASSERT_EQ(0, rbd_close(image)); + rados_ioctx_destroy(ioctx); +#endif +} + +namespace librbd { + +static bool operator==(const image_spec_t &lhs, const image_spec_t &rhs) { + return (lhs.id == rhs.id && lhs.name == rhs.name); +} + +static bool operator==(const linked_image_spec_t &lhs, + const linked_image_spec_t &rhs) { + return (lhs.pool_id == rhs.pool_id && + lhs.pool_name == rhs.pool_name && + lhs.pool_namespace == rhs.pool_namespace && + lhs.image_id == rhs.image_id && + lhs.image_name == rhs.image_name && + lhs.trash == rhs.trash); +} + +static bool operator==(const mirror_peer_t &lhs, const mirror_peer_t &rhs) { + return (lhs.uuid == rhs.uuid && + lhs.cluster_name == rhs.cluster_name && + lhs.client_name == rhs.client_name); +} + +static std::ostream& operator<<(std::ostream &os, const mirror_peer_t &peer) { + os << "uuid=" << peer.uuid << ", " + << "cluster=" << peer.cluster_name << ", " + << "client=" << peer.client_name; + return os; +} + +} // namespace librbd + +TEST_F(TestLibRBD, Mirror) { + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + + std::vector<librbd::mirror_peer_t> expected_peers; + std::vector<librbd::mirror_peer_t> peers; + ASSERT_EQ(0, rbd.mirror_peer_list(ioctx, &peers)); + ASSERT_EQ(expected_peers, peers); + + std::string uuid1; + ASSERT_EQ(-EINVAL, rbd.mirror_peer_add(ioctx, &uuid1, "cluster1", "client")); + + rbd_mirror_mode_t mirror_mode; + ASSERT_EQ(0, rbd.mirror_mode_get(ioctx, &mirror_mode)); + ASSERT_EQ(RBD_MIRROR_MODE_DISABLED, mirror_mode); + + ASSERT_EQ(0, rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_IMAGE)); + ASSERT_EQ(0, rbd.mirror_mode_get(ioctx, &mirror_mode)); + + // Add some images to the pool + int order = 0; + std::string parent_name = get_temp_image_name(); + std::string child_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(rbd, ioctx, parent_name.c_str(), 2 << 20, + &order)); + bool old_format; + uint64_t features; + ASSERT_EQ(0, get_features(&old_format, &features)); + if ((features & RBD_FEATURE_LAYERING) != 0) { + librbd::Image parent; + ASSERT_EQ(0, rbd.open(ioctx, parent, parent_name.c_str(), NULL)); + ASSERT_EQ(0, parent.snap_create("parent_snap")); + ASSERT_EQ(0, parent.close()); + ASSERT_EQ(0, rbd.open(ioctx, parent, parent_name.c_str(), "parent_snap")); + ASSERT_EQ(0, parent.snap_protect("parent_snap")); + ASSERT_EQ(0, parent.close()); + ASSERT_EQ(0, rbd.clone(ioctx, parent_name.c_str(), "parent_snap", ioctx, + child_name.c_str(), features, &order)); + } + + ASSERT_EQ(RBD_MIRROR_MODE_IMAGE, mirror_mode); + + ASSERT_EQ(0, rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_POOL)); + ASSERT_EQ(0, rbd.mirror_mode_get(ioctx, &mirror_mode)); + ASSERT_EQ(RBD_MIRROR_MODE_POOL, mirror_mode); + + std::string uuid2; + std::string uuid3; + ASSERT_EQ(0, rbd.mirror_peer_add(ioctx, &uuid1, "cluster1", "client")); + ASSERT_EQ(0, rbd.mirror_peer_add(ioctx, &uuid2, "cluster2", "admin")); + ASSERT_EQ(-EEXIST, rbd.mirror_peer_add(ioctx, &uuid3, "cluster1", "foo")); + ASSERT_EQ(0, rbd.mirror_peer_add(ioctx, &uuid3, "cluster3", "admin")); + + ASSERT_EQ(0, rbd.mirror_peer_list(ioctx, &peers)); + auto sort_peers = [](const librbd::mirror_peer_t &lhs, + const librbd::mirror_peer_t &rhs) { + return lhs.uuid < rhs.uuid; + }; + expected_peers = { + {uuid1, "cluster1", "client"}, + {uuid2, "cluster2", "admin"}, + {uuid3, "cluster3", "admin"}}; + std::sort(expected_peers.begin(), expected_peers.end(), sort_peers); + ASSERT_EQ(expected_peers, peers); + + ASSERT_EQ(0, rbd.mirror_peer_remove(ioctx, "uuid4")); + ASSERT_EQ(0, rbd.mirror_peer_remove(ioctx, uuid2)); + + ASSERT_EQ(-ENOENT, rbd.mirror_peer_set_client(ioctx, "uuid4", "new client")); + ASSERT_EQ(0, rbd.mirror_peer_set_client(ioctx, uuid1, "new client")); + + ASSERT_EQ(-ENOENT, rbd.mirror_peer_set_cluster(ioctx, "uuid4", + "new cluster")); + ASSERT_EQ(0, rbd.mirror_peer_set_cluster(ioctx, uuid3, "new cluster")); + + ASSERT_EQ(0, rbd.mirror_peer_list(ioctx, &peers)); + expected_peers = { + {uuid1, "cluster1", "new client"}, + {uuid3, "new cluster", "admin"}}; + std::sort(expected_peers.begin(), expected_peers.end(), sort_peers); + ASSERT_EQ(expected_peers, peers); + + ASSERT_EQ(-EBUSY, rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_DISABLED)); + ASSERT_EQ(0, rbd.mirror_peer_remove(ioctx, uuid1)); + ASSERT_EQ(0, rbd.mirror_peer_remove(ioctx, uuid3)); + ASSERT_EQ(0, rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_DISABLED)); +} + +TEST_F(TestLibRBD, MirrorPeerAttributes) { + REQUIRE(!is_librados_test_stub(_rados)); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + ASSERT_EQ(0, rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_IMAGE)); + + std::string uuid; + ASSERT_EQ(0, rbd.mirror_peer_add(ioctx, &uuid, "remote_cluster", "client")); + + std::map<std::string, std::string> attributes; + ASSERT_EQ(-ENOENT, rbd.mirror_peer_get_attributes(ioctx, uuid, &attributes)); + ASSERT_EQ(-ENOENT, rbd.mirror_peer_set_attributes(ioctx, "missing uuid", + attributes)); + + std::map<std::string, std::string> expected_attributes{ + {"mon_host", "1.2.3.4"}, + {"key", "ABC"}}; + ASSERT_EQ(0, rbd.mirror_peer_set_attributes(ioctx, uuid, + expected_attributes)); + + ASSERT_EQ(0, rbd.mirror_peer_get_attributes(ioctx, uuid, + &attributes)); + ASSERT_EQ(expected_attributes, attributes); + + ASSERT_EQ(0, rbd.mirror_peer_remove(ioctx, uuid)); + ASSERT_EQ(0, rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_DISABLED)); +} + +TEST_F(TestLibRBD, FlushCacheWithCopyupOnExternalSnapshot) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image image; + std::string name = get_temp_image_name(); + + uint64_t size = 1 << 18; + int order = 0; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + bufferlist bl; + bl.append(std::string(size, '1')); + ASSERT_EQ((int)size, image.write(0, size, bl)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + + std::string clone_name = this->get_temp_image_name(); + ASSERT_EQ(0, rbd.clone(ioctx, name.c_str(), "one", ioctx, clone_name.c_str(), + RBD_FEATURE_LAYERING, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, clone_name.c_str(), NULL)); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, clone_name.c_str(), NULL)); + + // prepare CoW writeback that will be flushed on next op + bl.clear(); + bl.append(std::string(1, '1')); + ASSERT_EQ(0, image.flush()); + ASSERT_EQ(1, image.write(0, 1, bl)); + ASSERT_EQ(0, image2.snap_create("snap1")); + + librbd::RBD::AioCompletion *read_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + bufferlist read_bl; + image.aio_read(0, 1024, read_bl, read_comp); + ASSERT_EQ(0, read_comp->wait_for_complete()); + read_comp->release(); +} + +TEST_F(TestLibRBD, ExclusiveLock) +{ + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + static char buf[10]; + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + + rbd_image_t image1; + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image1, NULL)); + + int lock_owner; + ASSERT_EQ(0, rbd_lock_acquire(image1, RBD_LOCK_MODE_EXCLUSIVE)); + ASSERT_EQ(0, rbd_is_exclusive_lock_owner(image1, &lock_owner)); + ASSERT_TRUE(lock_owner); + + rbd_lock_mode_t lock_mode; + char *lock_owners[1]; + size_t max_lock_owners = 0; + ASSERT_EQ(-ERANGE, rbd_lock_get_owners(image1, &lock_mode, lock_owners, + &max_lock_owners)); + ASSERT_EQ(1U, max_lock_owners); + + ASSERT_EQ(0, rbd_lock_get_owners(image1, &lock_mode, lock_owners, + &max_lock_owners)); + ASSERT_EQ(RBD_LOCK_MODE_EXCLUSIVE, lock_mode); + ASSERT_STRNE("", lock_owners[0]); + ASSERT_EQ(1U, max_lock_owners); + + rbd_image_t image2; + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image2, NULL)); + + ASSERT_EQ(0, rbd_is_exclusive_lock_owner(image2, &lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(-EOPNOTSUPP, rbd_lock_break(image1, RBD_LOCK_MODE_SHARED, "")); + ASSERT_EQ(-EBUSY, rbd_lock_break(image1, RBD_LOCK_MODE_EXCLUSIVE, + "not the owner")); + + ASSERT_EQ(0, rbd_lock_release(image1)); + ASSERT_EQ(0, rbd_is_exclusive_lock_owner(image1, &lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(-ENOENT, rbd_lock_break(image1, RBD_LOCK_MODE_EXCLUSIVE, + lock_owners[0])); + rbd_lock_get_owners_cleanup(lock_owners, max_lock_owners); + + ASSERT_EQ(-EROFS, rbd_write(image1, 0, sizeof(buf), buf)); + ASSERT_EQ((ssize_t)sizeof(buf), rbd_write(image2, 0, sizeof(buf), buf)); + + ASSERT_EQ(0, rbd_lock_acquire(image2, RBD_LOCK_MODE_EXCLUSIVE)); + ASSERT_EQ(0, rbd_is_exclusive_lock_owner(image2, &lock_owner)); + ASSERT_TRUE(lock_owner); + + ASSERT_EQ(0, rbd_lock_release(image2)); + ASSERT_EQ(0, rbd_is_exclusive_lock_owner(image2, &lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(0, rbd_lock_acquire(image1, RBD_LOCK_MODE_EXCLUSIVE)); + ASSERT_EQ(0, rbd_is_exclusive_lock_owner(image1, &lock_owner)); + ASSERT_TRUE(lock_owner); + + ASSERT_EQ((ssize_t)sizeof(buf), rbd_write(image1, 0, sizeof(buf), buf)); + ASSERT_EQ(-EROFS, rbd_write(image2, 0, sizeof(buf), buf)); + + ASSERT_EQ(0, rbd_lock_release(image1)); + ASSERT_EQ(0, rbd_is_exclusive_lock_owner(image1, &lock_owner)); + ASSERT_FALSE(lock_owner); + + int owner_id = -1; + mutex lock; + const auto pingpong = [&](int m_id, rbd_image_t &m_image) { + for (int i = 0; i < 10; i++) { + { + lock_guard<mutex> locker(lock); + if (owner_id == m_id) { + std::cout << m_id << ": releasing exclusive lock" << std::endl; + EXPECT_EQ(0, rbd_lock_release(m_image)); + int lock_owner; + EXPECT_EQ(0, rbd_is_exclusive_lock_owner(m_image, &lock_owner)); + EXPECT_FALSE(lock_owner); + owner_id = -1; + std::cout << m_id << ": exclusive lock released" << std::endl; + continue; + } + } + + std::cout << m_id << ": acquiring exclusive lock" << std::endl; + int r; + do { + r = rbd_lock_acquire(m_image, RBD_LOCK_MODE_EXCLUSIVE); + if (r == -EROFS) { + usleep(1000); + } + } while (r == -EROFS); + EXPECT_EQ(0, r); + + int lock_owner; + EXPECT_EQ(0, rbd_is_exclusive_lock_owner(m_image, &lock_owner)); + EXPECT_TRUE(lock_owner); + std::cout << m_id << ": exclusive lock acquired" << std::endl; + { + lock_guard<mutex> locker(lock); + owner_id = m_id; + } + usleep(rand() % 50000); + } + + lock_guard<mutex> locker(lock); + if (owner_id == m_id) { + EXPECT_EQ(0, rbd_lock_release(m_image)); + int lock_owner; + EXPECT_EQ(0, rbd_is_exclusive_lock_owner(m_image, &lock_owner)); + EXPECT_FALSE(lock_owner); + owner_id = -1; + } + }; + thread ping(bind(pingpong, 1, ref(image1))); + thread pong(bind(pingpong, 2, ref(image2))); + + ping.join(); + pong.join(); + + ASSERT_EQ(0, rbd_lock_acquire(image2, RBD_LOCK_MODE_EXCLUSIVE)); + ASSERT_EQ(0, rbd_is_exclusive_lock_owner(image2, &lock_owner)); + ASSERT_TRUE(lock_owner); + + ASSERT_EQ(0, rbd_close(image2)); + + ASSERT_EQ(0, rbd_lock_acquire(image1, RBD_LOCK_MODE_EXCLUSIVE)); + ASSERT_EQ(0, rbd_is_exclusive_lock_owner(image1, &lock_owner)); + ASSERT_TRUE(lock_owner); + + ASSERT_EQ(0, rbd_close(image1)); + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, BreakLock) +{ + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + static char buf[10]; + + rados_t blacklist_cluster; + ASSERT_EQ("", connect_cluster(&blacklist_cluster)); + + rados_ioctx_t ioctx, blacklist_ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + ASSERT_EQ(0, rados_ioctx_create(blacklist_cluster, m_pool_name.c_str(), + &blacklist_ioctx)); + + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + + rbd_image_t image, blacklist_image; + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + ASSERT_EQ(0, rbd_open(blacklist_ioctx, name.c_str(), &blacklist_image, NULL)); + + ASSERT_EQ(0, rbd_metadata_set(image, "conf_rbd_blacklist_on_break_lock", "true")); + ASSERT_EQ(0, rbd_lock_acquire(blacklist_image, RBD_LOCK_MODE_EXCLUSIVE)); + + rbd_lock_mode_t lock_mode; + char *lock_owners[1]; + size_t max_lock_owners = 1; + ASSERT_EQ(0, rbd_lock_get_owners(image, &lock_mode, lock_owners, + &max_lock_owners)); + ASSERT_EQ(RBD_LOCK_MODE_EXCLUSIVE, lock_mode); + ASSERT_STRNE("", lock_owners[0]); + ASSERT_EQ(1U, max_lock_owners); + + ASSERT_EQ(0, rbd_lock_break(image, RBD_LOCK_MODE_EXCLUSIVE, lock_owners[0])); + ASSERT_EQ(0, rbd_lock_acquire(image, RBD_LOCK_MODE_EXCLUSIVE)); + EXPECT_EQ(0, rados_wait_for_latest_osdmap(blacklist_cluster)); + + ASSERT_EQ((ssize_t)sizeof(buf), rbd_write(image, 0, sizeof(buf), buf)); + ASSERT_EQ(-EBLACKLISTED, rbd_write(blacklist_image, 0, sizeof(buf), buf)); + + ASSERT_EQ(0, rbd_close(image)); + ASSERT_EQ(0, rbd_close(blacklist_image)); + + rbd_lock_get_owners_cleanup(lock_owners, max_lock_owners); + + rados_ioctx_destroy(ioctx); + rados_ioctx_destroy(blacklist_ioctx); + rados_shutdown(blacklist_cluster); +} + +TEST_F(TestLibRBD, DiscardAfterWrite) +{ + REQUIRE(!is_skip_partial_discard_enabled()); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + uint64_t size = 1 << 20; + int order = 18; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + // enable writeback cache + ASSERT_EQ(0, image.flush()); + + bufferlist bl; + bl.append(std::string(256, '1')); + + librbd::RBD::AioCompletion *write_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, image.aio_write(0, bl.length(), bl, write_comp)); + ASSERT_EQ(0, write_comp->wait_for_complete()); + write_comp->release(); + + librbd::RBD::AioCompletion *discard_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, image.aio_discard(0, 256, discard_comp)); + ASSERT_EQ(0, discard_comp->wait_for_complete()); + discard_comp->release(); + + librbd::RBD::AioCompletion *read_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + bufferlist read_bl; + image.aio_read(0, bl.length(), read_bl, read_comp); + ASSERT_EQ(0, read_comp->wait_for_complete()); + ASSERT_EQ(bl.length(), read_comp->get_return_value()); + ASSERT_TRUE(read_bl.is_zero()); + read_comp->release(); +} + +TEST_F(TestLibRBD, DefaultFeatures) { + std::string orig_default_features; + ASSERT_EQ(0, _rados.conf_get("rbd_default_features", orig_default_features)); + BOOST_SCOPE_EXIT_ALL(orig_default_features) { + ASSERT_EQ(0, _rados.conf_set("rbd_default_features", + orig_default_features.c_str())); + }; + + std::list<std::pair<std::string, std::string> > feature_names_to_bitmask = { + {"", orig_default_features}, + {"layering", "1"}, + {"layering, exclusive-lock", "5"}, + {"exclusive-lock,journaling", "68"}, + {"125", "125"} + }; + + for (auto &pair : feature_names_to_bitmask) { + ASSERT_EQ(0, _rados.conf_set("rbd_default_features", pair.first.c_str())); + std::string features; + ASSERT_EQ(0, _rados.conf_get("rbd_default_features", features)); + ASSERT_EQ(pair.second, features); + } +} + +TEST_F(TestLibRBD, TestTrashMoveAndPurge) { + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + + uint64_t size = 1 << 18; + int order = 12; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + + std::string image_id; + ASSERT_EQ(0, image.get_id(&image_id)); + image.close(); + + ASSERT_EQ(0, rbd.trash_move(ioctx, name.c_str(), 0)); + + std::vector<std::string> images; + ASSERT_EQ(0, rbd.list(ioctx, images)); + for (const auto& image : images) { + ASSERT_TRUE(image != name); + } + + librbd::trash_image_info_t info; + ASSERT_EQ(-ENOENT, rbd.trash_get(ioctx, "dummy image id", &info)); + ASSERT_EQ(0, rbd.trash_get(ioctx, image_id.c_str(), &info)); + ASSERT_EQ(image_id, info.id); + + std::vector<librbd::trash_image_info_t> entries; + ASSERT_EQ(0, rbd.trash_list(ioctx, entries)); + ASSERT_FALSE(entries.empty()); + ASSERT_EQ(entries.begin()->id, image_id); + + entries.clear(); + PrintProgress pp; + ASSERT_EQ(0, rbd.trash_remove_with_progress(ioctx, image_id.c_str(), + false, pp)); + ASSERT_EQ(0, rbd.trash_list(ioctx, entries)); + ASSERT_TRUE(entries.empty()); +} + +TEST_F(TestLibRBD, TestTrashMoveAndPurgeNonExpiredDelay) { + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + + uint64_t size = 1 << 18; + int order = 12; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + + std::string image_id; + ASSERT_EQ(0, image.get_id(&image_id)); + image.close(); + + ASSERT_EQ(0, rbd.trash_move(ioctx, name.c_str(), 100)); + + PrintProgress pp; + ASSERT_EQ(-EPERM, rbd.trash_remove_with_progress(ioctx, image_id.c_str(), + false, pp)); + + PrintProgress pp2; + ASSERT_EQ(0, rbd.trash_remove_with_progress(ioctx, image_id.c_str(), + true, pp2)); +} + +TEST_F(TestLibRBD, TestTrashPurge) { + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name1 = get_temp_image_name(); + std::string name2 = get_temp_image_name(); + + uint64_t size = 1 << 18; + int order = 12; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name1.c_str(), size, &order)); + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name2.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name1.c_str(), nullptr)); + + std::string image_id1; + ASSERT_EQ(0, image1.get_id(&image_id1)); + image1.close(); + + ASSERT_EQ(0, rbd.trash_move(ioctx, name1.c_str(), 0)); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name2.c_str(), nullptr)); + ceph::bufferlist bl; + bl.append(std::string(1024, '0')); + ASSERT_EQ(1024, image2.write(0, 1024, bl)); + + std::string image_id2; + ASSERT_EQ(0, image2.get_id(&image_id2)); + image2.close(); + + ASSERT_EQ(0, rbd.trash_move(ioctx, name2.c_str(), 100)); + ASSERT_EQ(0, rbd.trash_purge(ioctx, 0, -1)); + + std::vector<librbd::trash_image_info_t> entries; + ASSERT_EQ(0, rbd.trash_list(ioctx, entries)); + ASSERT_EQ(1U, entries.size()); + ASSERT_EQ(image_id2, entries[0].id); + ASSERT_EQ(name2, entries[0].name); + entries.clear(); + + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + float threshold = 0.0; + if (!is_librados_test_stub(_rados)) { + // real cluster usage reports have a long latency to update + threshold = -1.0; + } + + ASSERT_EQ(0, rbd.trash_purge(ioctx, now.tv_sec+1000, threshold)); + ASSERT_EQ(0, rbd.trash_list(ioctx, entries)); + ASSERT_EQ(0U, entries.size()); +} + +TEST_F(TestLibRBD, TestTrashMoveAndRestore) { + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + + uint64_t size = 1 << 18; + int order = 12; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + + std::string image_id; + ASSERT_EQ(0, image.get_id(&image_id)); + image.close(); + + ASSERT_EQ(0, rbd.trash_move(ioctx, name.c_str(), 10)); + + std::vector<std::string> images; + ASSERT_EQ(0, rbd.list(ioctx, images)); + for (const auto& image : images) { + ASSERT_TRUE(image != name); + } + + std::vector<librbd::trash_image_info_t> entries; + ASSERT_EQ(0, rbd.trash_list(ioctx, entries)); + ASSERT_FALSE(entries.empty()); + ASSERT_EQ(entries.begin()->id, image_id); + + images.clear(); + ASSERT_EQ(0, rbd.trash_restore(ioctx, image_id.c_str(), "")); + ASSERT_EQ(0, rbd.list(ioctx, images)); + ASSERT_FALSE(images.empty()); + bool found = false; + for (const auto& image : images) { + if (image == name) { + found = true; + break; + } + } + ASSERT_TRUE(found); +} + +TEST_F(TestLibRBD, TestListWatchers) { + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + + uint64_t size = 1 << 18; + int order = 12; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + std::list<librbd::image_watcher_t> watchers; + + // No watchers + ASSERT_EQ(0, rbd.open_read_only(ioctx, image, name.c_str(), nullptr)); + ASSERT_EQ(0, image.list_watchers(watchers)); + ASSERT_EQ(0U, watchers.size()); + ASSERT_EQ(0, image.close()); + + // One watcher + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + ASSERT_EQ(0, image.list_watchers(watchers)); + ASSERT_EQ(1U, watchers.size()); + ASSERT_EQ(0, image.close()); +} + +TEST_F(TestLibRBD, TestSetSnapById) { + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string name = get_temp_image_name(); + + uint64_t size = 1 << 18; + int order = 12; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + ASSERT_EQ(0, image.snap_create("snap")); + + vector<librbd::snap_info_t> snaps; + ASSERT_EQ(0, image.snap_list(snaps)); + ASSERT_EQ(1U, snaps.size()); + + ASSERT_EQ(0, image.snap_set_by_id(snaps[0].id)); + ASSERT_EQ(0, image.snap_set_by_id(CEPH_NOSNAP)); +} + +TEST_F(TestLibRBD, Namespaces) { + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + rados_remove(ioctx, RBD_NAMESPACE); + + ASSERT_EQ(0, rbd_namespace_create(ioctx, "name1")); + ASSERT_EQ(0, rbd_namespace_create(ioctx, "name2")); + ASSERT_EQ(0, rbd_namespace_create(ioctx, "name3")); + ASSERT_EQ(0, rbd_namespace_remove(ioctx, "name2")); + + char names[1024]; + size_t max_size = sizeof(names); + int len = rbd_namespace_list(ioctx, names, &max_size); + + std::vector<std::string> cpp_names; + for (char* cur_name = names; cur_name < names + len; ) { + cpp_names.push_back(cur_name); + cur_name += strlen(cur_name) + 1; + } + ASSERT_EQ(2U, cpp_names.size()); + ASSERT_EQ("name1", cpp_names[0]); + ASSERT_EQ("name3", cpp_names[1]); + bool exists; + ASSERT_EQ(0, rbd_namespace_exists(ioctx, "name2", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, rbd_namespace_exists(ioctx, "name3", &exists)); + ASSERT_TRUE(exists); + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, NamespacesPP) { + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + ioctx.remove(RBD_NAMESPACE); + + librbd::RBD rbd; + ASSERT_EQ(-EINVAL, rbd.namespace_create(ioctx, "")); + ASSERT_EQ(-EINVAL, rbd.namespace_remove(ioctx, "")); + + ASSERT_EQ(0, rbd.namespace_create(ioctx, "name1")); + ASSERT_EQ(-EEXIST, rbd.namespace_create(ioctx, "name1")); + ASSERT_EQ(0, rbd.namespace_create(ioctx, "name2")); + ASSERT_EQ(0, rbd.namespace_create(ioctx, "name3")); + ASSERT_EQ(0, rbd.namespace_remove(ioctx, "name2")); + ASSERT_EQ(-ENOENT, rbd.namespace_remove(ioctx, "name2")); + + std::vector<std::string> names; + ASSERT_EQ(0, rbd.namespace_list(ioctx, &names)); + ASSERT_EQ(2U, names.size()); + ASSERT_EQ("name1", names[0]); + ASSERT_EQ("name3", names[1]); + bool exists; + ASSERT_EQ(0, rbd.namespace_exists(ioctx, "name2", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, rbd.namespace_exists(ioctx, "name3", &exists)); + ASSERT_TRUE(exists); + + librados::IoCtx ns_io_ctx; + ns_io_ctx.dup(ioctx); + + std::string name = get_temp_image_name(); + int order = 0; + uint64_t features = 0; + if (!get_features(&features)) { + // old format doesn't support namespaces + ns_io_ctx.set_namespace("name1"); + ASSERT_EQ(-EINVAL, create_image_pp(rbd, ns_io_ctx, name.c_str(), 0, + &order)); + return; + } + + ns_io_ctx.set_namespace("missing"); + ASSERT_EQ(-ENOENT, create_image_pp(rbd, ns_io_ctx, name.c_str(), 0, &order)); + + ns_io_ctx.set_namespace("name1"); + ASSERT_EQ(0, create_image_pp(rbd, ns_io_ctx, name.c_str(), 0, &order)); + ASSERT_EQ(-EBUSY, rbd.namespace_remove(ns_io_ctx, "name1")); + + std::string image_id; + { + librbd::Image image; + ASSERT_EQ(-ENOENT, rbd.open(ioctx, image, name.c_str(), NULL)); + ASSERT_EQ(0, rbd.open(ns_io_ctx, image, name.c_str(), NULL)); + ASSERT_EQ(0, get_image_id(image, &image_id)); + } + + ASSERT_EQ(-ENOENT, rbd.trash_move(ioctx, name.c_str(), 0)); + ASSERT_EQ(0, rbd.trash_move(ns_io_ctx, name.c_str(), 0)); + ASSERT_EQ(-EBUSY, rbd.namespace_remove(ns_io_ctx, "name1")); + + PrintProgress pp; + ASSERT_EQ(-ENOENT, rbd.trash_remove_with_progress(ioctx, image_id.c_str(), + false, pp)); + ASSERT_EQ(0, rbd.trash_remove_with_progress(ns_io_ctx, image_id.c_str(), + false, pp)); + ASSERT_EQ(0, rbd.namespace_remove(ns_io_ctx, "name1")); + + names.clear(); + ASSERT_EQ(0, rbd.namespace_list(ioctx, &names)); + ASSERT_EQ(1U, names.size()); + ASSERT_EQ("name3", names[0]); +} + +TEST_F(TestLibRBD, Migration) { + bool old_format; + uint64_t features; + ASSERT_EQ(0, get_features(&old_format, &features)); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + BOOST_SCOPE_EXIT(&ioctx) { + rados_ioctx_destroy(ioctx); + } BOOST_SCOPE_EXIT_END; + + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + + rbd_image_options_t image_options; + rbd_image_options_create(&image_options); + BOOST_SCOPE_EXIT(&image_options) { + rbd_image_options_destroy(image_options); + } BOOST_SCOPE_EXIT_END; + + ASSERT_EQ(0, rbd_migration_prepare(ioctx, name.c_str(), ioctx, name.c_str(), + image_options)); + + rbd_image_migration_status_t status; + ASSERT_EQ(0, rbd_migration_status(ioctx, name.c_str(), &status, + sizeof(status))); + ASSERT_EQ(status.source_pool_id, rados_ioctx_get_id(ioctx)); + ASSERT_EQ(status.source_image_name, name); + if (old_format) { + ASSERT_EQ(status.source_image_id, string()); + } else { + ASSERT_NE(status.source_image_id, string()); + ASSERT_EQ(-EROFS, rbd_trash_remove(ioctx, status.source_image_id, false)); + ASSERT_EQ(-EINVAL, rbd_trash_restore(ioctx, status.source_image_id, name.c_str())); + } + ASSERT_EQ(status.dest_pool_id, rados_ioctx_get_id(ioctx)); + ASSERT_EQ(status.dest_image_name, name); + ASSERT_NE(status.dest_image_id, string()); + ASSERT_EQ(status.state, RBD_IMAGE_MIGRATION_STATE_PREPARED); + rbd_migration_status_cleanup(&status); + + ASSERT_EQ(-EBUSY, rbd_remove(ioctx, name.c_str())); + ASSERT_EQ(-EBUSY, rbd_trash_move(ioctx, name.c_str(), 0)); + + ASSERT_EQ(0, rbd_migration_execute(ioctx, name.c_str())); + + ASSERT_EQ(0, rbd_migration_status(ioctx, name.c_str(), &status, + sizeof(status))); + ASSERT_EQ(status.state, RBD_IMAGE_MIGRATION_STATE_EXECUTED); + rbd_migration_status_cleanup(&status); + + ASSERT_EQ(0, rbd_migration_commit(ioctx, name.c_str())); + + std::string new_name = get_temp_image_name(); + + ASSERT_EQ(0, rbd_migration_prepare(ioctx, name.c_str(), ioctx, + new_name.c_str(), image_options)); + + ASSERT_EQ(-EBUSY, rbd_remove(ioctx, new_name.c_str())); + ASSERT_EQ(-EBUSY, rbd_trash_move(ioctx, new_name.c_str(), 0)); + + ASSERT_EQ(0, rbd_migration_abort(ioctx, name.c_str())); + + rbd_image_t image; + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + EXPECT_EQ(0, rbd_close(image)); +} + +TEST_F(TestLibRBD, MigrationPP) { + bool old_format; + uint64_t features; + ASSERT_EQ(0, get_features(&old_format, &features)); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + librbd::RBD rbd; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::ImageOptions image_options; + + ASSERT_EQ(0, rbd.migration_prepare(ioctx, name.c_str(), ioctx, name.c_str(), + image_options)); + + librbd::image_migration_status_t status; + ASSERT_EQ(0, rbd.migration_status(ioctx, name.c_str(), &status, + sizeof(status))); + ASSERT_EQ(status.source_pool_id, ioctx.get_id()); + ASSERT_EQ(status.source_image_name, name); + if (old_format) { + ASSERT_EQ(status.source_image_id, ""); + } else { + ASSERT_NE(status.source_image_id, ""); + ASSERT_EQ(-EROFS, rbd.trash_remove(ioctx, status.source_image_id.c_str(), false)); + ASSERT_EQ(-EINVAL, rbd.trash_restore(ioctx, status.source_image_id.c_str(), name.c_str())); + } + ASSERT_EQ(status.dest_pool_id, ioctx.get_id()); + ASSERT_EQ(status.dest_image_name, name); + ASSERT_NE(status.dest_image_id, ""); + ASSERT_EQ(status.state, RBD_IMAGE_MIGRATION_STATE_PREPARED); + + ASSERT_EQ(-EBUSY, rbd.remove(ioctx, name.c_str())); + ASSERT_EQ(-EBUSY, rbd.trash_move(ioctx, name.c_str(), 0)); + + ASSERT_EQ(0, rbd.migration_execute(ioctx, name.c_str())); + + ASSERT_EQ(0, rbd.migration_status(ioctx, name.c_str(), &status, + sizeof(status))); + ASSERT_EQ(status.state, RBD_IMAGE_MIGRATION_STATE_EXECUTED); + + ASSERT_EQ(0, rbd.migration_commit(ioctx, name.c_str())); + + std::string new_name = get_temp_image_name(); + + ASSERT_EQ(0, rbd.migration_prepare(ioctx, name.c_str(), ioctx, + new_name.c_str(), image_options)); + + ASSERT_EQ(-EBUSY, rbd.remove(ioctx, new_name.c_str())); + ASSERT_EQ(-EBUSY, rbd.trash_move(ioctx, new_name.c_str(), 0)); + + ASSERT_EQ(0, rbd.migration_abort(ioctx, name.c_str())); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); +} + +TEST_F(TestLibRBD, TestGetAccessTimestamp) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + struct timespec timestamp; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_get_access_timestamp(image, ×tamp)); + ASSERT_LT(0, timestamp.tv_sec); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, TestGetModifyTimestamp) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + struct timespec timestamp; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + ASSERT_EQ(0, rbd_get_modify_timestamp(image, ×tamp)); + ASSERT_LT(0, timestamp.tv_sec); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, ZeroOverlapFlatten) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image parent_image; + std::string name = get_temp_image_name(); + + uint64_t size = 1; + int order = 0; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, parent_image, name.c_str(), NULL)); + + uint64_t features; + ASSERT_EQ(0, parent_image.features(&features)); + + ASSERT_EQ(0, parent_image.snap_create("snap")); + ASSERT_EQ(0, parent_image.snap_protect("snap")); + + std::string clone_name = this->get_temp_image_name(); + ASSERT_EQ(0, rbd.clone(ioctx, name.c_str(), "snap", ioctx, clone_name.c_str(), + features, &order)); + + librbd::Image clone_image; + ASSERT_EQ(0, rbd.open(ioctx, clone_image, clone_name.c_str(), NULL)); + ASSERT_EQ(0, clone_image.resize(0)); + ASSERT_EQ(0, clone_image.flatten()); +} + +TEST_F(TestLibRBD, PoolMetadata) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + char keys[1024]; + char vals[1024]; + size_t keys_len = sizeof(keys); + size_t vals_len = sizeof(vals); + + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + + ASSERT_EQ(0, rbd_pool_metadata_list(ioctx, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(0U, keys_len); + ASSERT_EQ(0U, vals_len); + + char value[1024]; + size_t value_len = sizeof(value); + memset_rand(value, value_len); + + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "key1", "value1")); + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "key2", "value2")); + ASSERT_EQ(0, rbd_pool_metadata_get(ioctx, "key1", value, &value_len)); + ASSERT_STREQ(value, "value1"); + value_len = 1; + ASSERT_EQ(-ERANGE, rbd_pool_metadata_get(ioctx, "key1", value, &value_len)); + ASSERT_EQ(value_len, strlen("value1") + 1); + + ASSERT_EQ(-ERANGE, rbd_pool_metadata_list(ioctx, "", 0, keys, &keys_len, vals, + &vals_len)); + keys_len = sizeof(keys); + vals_len = sizeof(vals); + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_pool_metadata_list(ioctx, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1 + strlen("key2") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1 + strlen("value2") + 1); + ASSERT_STREQ(keys, "key1"); + ASSERT_STREQ(keys + strlen(keys) + 1, "key2"); + ASSERT_STREQ(vals, "value1"); + ASSERT_STREQ(vals + strlen(vals) + 1, "value2"); + + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "key1")); + ASSERT_EQ(-ENOENT, rbd_pool_metadata_remove(ioctx, "key3")); + value_len = sizeof(value); + ASSERT_EQ(-ENOENT, rbd_pool_metadata_get(ioctx, "key3", value, &value_len)); + ASSERT_EQ(0, rbd_pool_metadata_list(ioctx, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key2") + 1); + ASSERT_EQ(vals_len, strlen("value2") + 1); + ASSERT_STREQ(keys, "key2"); + ASSERT_STREQ(vals, "value2"); + + // test config setting + ASSERT_EQ(-EINVAL, rbd_pool_metadata_set(ioctx, "conf_UNKNOWN", "false")); + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "conf_rbd_cache", "false")); + ASSERT_EQ(-EINVAL, rbd_pool_metadata_set(ioctx, "conf_rbd_cache", "INVALID")); + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "conf_rbd_cache")); + + // test short buffer cases + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "key1", "value1")); + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "key3", "value3")); + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "key4", "value4")); + + keys_len = strlen("key1") + 1; + vals_len = strlen("value1") + 1; + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_pool_metadata_list(ioctx, "", 1, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1); + ASSERT_STREQ(keys, "key1"); + ASSERT_STREQ(vals, "value1"); + + ASSERT_EQ(-ERANGE, rbd_pool_metadata_list(ioctx, "", 2, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1 + strlen("key2") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1 + strlen("value2") + 1); + + ASSERT_EQ(-ERANGE, rbd_pool_metadata_list(ioctx, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1 + strlen("key2") + 1 + strlen("key3") + + 1 + strlen("key4") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1 + strlen("value2") + 1 + + strlen("value3") + 1 + strlen("value4") + 1); + + // test `start` param + keys_len = sizeof(keys); + vals_len = sizeof(vals); + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_pool_metadata_list(ioctx, "key2", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key3") + 1 + strlen("key4") + 1); + ASSERT_EQ(vals_len, strlen("value3") + 1 + strlen("value4") + 1); + ASSERT_STREQ(keys, "key3"); + ASSERT_STREQ(vals, "value3"); + + //cleanup + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "key1")); + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "key2")); + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "key3")); + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "key4")); + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, PoolMetadataPP) +{ + REQUIRE_FORMAT_V2(); + + librbd::RBD rbd; + string value; + map<string, bufferlist> pairs; + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + ASSERT_EQ(0, rbd.pool_metadata_list(ioctx, "", 0, &pairs)); + ASSERT_TRUE(pairs.empty()); + + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "key1", "value1")); + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "key2", "value2")); + ASSERT_EQ(0, rbd.pool_metadata_get(ioctx, "key1", &value)); + ASSERT_EQ(value, "value1"); + ASSERT_EQ(0, rbd.pool_metadata_list(ioctx, "", 0, &pairs)); + ASSERT_EQ(2U, pairs.size()); + ASSERT_EQ(0, strncmp("value1", pairs["key1"].c_str(), 6)); + ASSERT_EQ(0, strncmp("value2", pairs["key2"].c_str(), 6)); + + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "key1")); + ASSERT_EQ(-ENOENT, rbd.pool_metadata_remove(ioctx, "key3")); + ASSERT_EQ(-ENOENT, rbd.pool_metadata_get(ioctx, "key3", &value)); + pairs.clear(); + ASSERT_EQ(0, rbd.pool_metadata_list(ioctx, "", 0, &pairs)); + ASSERT_EQ(1U, pairs.size()); + ASSERT_EQ(0, strncmp("value2", pairs["key2"].c_str(), 6)); + + // test `start` param + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "key1", "value1")); + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "key3", "value3")); + + pairs.clear(); + ASSERT_EQ(0, rbd.pool_metadata_list(ioctx, "key2", 0, &pairs)); + ASSERT_EQ(1U, pairs.size()); + ASSERT_EQ(0, strncmp("value3", pairs["key3"].c_str(), 6)); + + // test config setting + ASSERT_EQ(-EINVAL, rbd.pool_metadata_set(ioctx, "conf_UNKNOWN", "false")); + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "conf_rbd_cache", "false")); + ASSERT_EQ(-EINVAL, rbd.pool_metadata_set(ioctx, "conf_rbd_cache", "INVALID")); + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "conf_rbd_cache")); + + // cleanup + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "key1")); + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "key2")); + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "key3")); +} + +TEST_F(TestLibRBD, Config) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "conf_rbd_cache", "false")); + + rbd_config_option_t options[1024]; + int max_options = 0; + ASSERT_EQ(-ERANGE, rbd_config_pool_list(ioctx, options, &max_options)); + ASSERT_EQ(0, rbd_config_pool_list(ioctx, options, &max_options)); + ASSERT_GT(max_options, 0); + ASSERT_LT(max_options, 1024); + for (int i = 0; i < max_options; i++) { + if (options[i].name == std::string("rbd_cache")) { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_POOL); + ASSERT_STREQ("false", options[i].value); + } else { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG); + } + } + rbd_config_pool_list_cleanup(options, max_options); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_config_image_list(image, options, &max_options)); + for (int i = 0; i < max_options; i++) { + if (options[i].name == std::string("rbd_cache")) { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_POOL); + ASSERT_STREQ("false", options[i].value); + } else { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG); + } + } + rbd_config_image_list_cleanup(options, max_options); + + ASSERT_EQ(0, rbd_metadata_set(image, "conf_rbd_cache", "true")); + + ASSERT_EQ(0, rbd_config_image_list(image, options, &max_options)); + for (int i = 0; i < max_options; i++) { + if (options[i].name == std::string("rbd_cache")) { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_IMAGE); + ASSERT_STREQ("true", options[i].value); + } else { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG); + } + } + rbd_config_image_list_cleanup(options, max_options); + + ASSERT_EQ(0, rbd_metadata_remove(image, "conf_rbd_cache")); + + ASSERT_EQ(0, rbd_config_image_list(image, options, &max_options)); + for (int i = 0; i < max_options; i++) { + if (options[i].name == std::string("rbd_cache")) { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_POOL); + ASSERT_STREQ("false", options[i].value); + } else { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG); + } + } + rbd_config_image_list_cleanup(options, max_options); + + ASSERT_EQ(0, rbd_close(image)); + + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "conf_rbd_cache")); + + ASSERT_EQ(-ERANGE, rbd_config_pool_list(ioctx, options, &max_options)); + ASSERT_EQ(0, rbd_config_pool_list(ioctx, options, &max_options)); + for (int i = 0; i < max_options; i++) { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG); + } + rbd_config_pool_list_cleanup(options, max_options); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, ConfigPP) +{ + REQUIRE_FORMAT_V2(); + + librbd::RBD rbd; + string value; + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "conf_rbd_cache", "false")); + + std::vector<librbd::config_option_t> options; + ASSERT_EQ(0, rbd.config_list(ioctx, &options)); + for (auto &option : options) { + if (option.name == std::string("rbd_cache")) { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_POOL); + ASSERT_EQ("false", option.value); + } else { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG); + } + } + + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + + options.clear(); + ASSERT_EQ(0, image.config_list(&options)); + for (auto &option : options) { + if (option.name == std::string("rbd_cache")) { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_POOL); + ASSERT_EQ("false", option.value); + } else { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG); + } + } + + ASSERT_EQ(0, image.metadata_set("conf_rbd_cache", "true")); + + options.clear(); + ASSERT_EQ(0, image.config_list(&options)); + for (auto &option : options) { + if (option.name == std::string("rbd_cache")) { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_IMAGE); + ASSERT_EQ("true", option.value); + } else { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG); + } + } + + ASSERT_EQ(0, image.metadata_remove("conf_rbd_cache")); + + options.clear(); + ASSERT_EQ(0, image.config_list(&options)); + for (auto &option : options) { + if (option.name == std::string("rbd_cache")) { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_POOL); + ASSERT_EQ("false", option.value); + } else { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG); + } + } + + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "conf_rbd_cache")); + + options.clear(); + ASSERT_EQ(0, rbd.config_list(ioctx, &options)); + for (auto &option : options) { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG); + } +} + +TEST_F(TestLibRBD, PoolStatsPP) +{ + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(create_pool(true).c_str(), ioctx)); + + librbd::RBD rbd; + std::string image_name; + uint64_t size = 2 << 20; + uint64_t expected_size = 0; + for (size_t idx = 0; idx < 4; ++idx) { + image_name = get_temp_image_name(); + + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, image_name.c_str(), size, &order)); + expected_size += size; + } + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ(0, image.resize(0)); + ASSERT_EQ(0, image.close()); + uint64_t expect_head_size = (expected_size - size); + + uint64_t image_count; + uint64_t provisioned_bytes; + uint64_t max_provisioned_bytes; + uint64_t snap_count; + uint64_t trash_image_count; + uint64_t trash_provisioned_bytes; + uint64_t trash_max_provisioned_bytes; + uint64_t trash_snap_count; + + librbd::PoolStats pool_stats1; + pool_stats1.add(RBD_POOL_STAT_OPTION_IMAGES, &image_count); + pool_stats1.add(RBD_POOL_STAT_OPTION_IMAGE_PROVISIONED_BYTES, + &provisioned_bytes); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats1)); + + ASSERT_EQ(4U, image_count); + ASSERT_EQ(expect_head_size, provisioned_bytes); + + pool_stats1.add(RBD_POOL_STAT_OPTION_IMAGE_MAX_PROVISIONED_BYTES, + &max_provisioned_bytes); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats1)); + ASSERT_EQ(4U, image_count); + ASSERT_EQ(expect_head_size, provisioned_bytes); + ASSERT_EQ(expected_size, max_provisioned_bytes); + + librbd::PoolStats pool_stats2; + pool_stats2.add(RBD_POOL_STAT_OPTION_IMAGE_SNAPSHOTS, &snap_count); + pool_stats2.add(RBD_POOL_STAT_OPTION_TRASH_IMAGES, &trash_image_count); + pool_stats2.add(RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS, &trash_snap_count); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats2)); + ASSERT_EQ(1U, snap_count); + ASSERT_EQ(0U, trash_image_count); + ASSERT_EQ(0U, trash_snap_count); + + ASSERT_EQ(0, rbd.trash_move(ioctx, image_name.c_str(), 0)); + + librbd::PoolStats pool_stats3; + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_IMAGES, &trash_image_count); + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_PROVISIONED_BYTES, + &trash_provisioned_bytes); + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_MAX_PROVISIONED_BYTES, + &trash_max_provisioned_bytes); + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS, &trash_snap_count); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats3)); + ASSERT_EQ(1U, trash_image_count); + ASSERT_EQ(0U, trash_provisioned_bytes); + ASSERT_EQ(size, trash_max_provisioned_bytes); + ASSERT_EQ(1U, trash_snap_count); +} + +TEST_F(TestLibRBD, ImageSpec) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(create_pool(true).c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image parent_image; + std::string name = get_temp_image_name(); + + uint64_t size = 1; + int order = 0; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, parent_image, name.c_str(), NULL)); + + std::string parent_id; + ASSERT_EQ(0, parent_image.get_id(&parent_id)); + + uint64_t features; + ASSERT_EQ(0, parent_image.features(&features)); + + ASSERT_EQ(0, parent_image.snap_create("snap")); + ASSERT_EQ(0, parent_image.snap_protect("snap")); + + std::string clone_name = this->get_temp_image_name(); + ASSERT_EQ(0, rbd.clone(ioctx, name.c_str(), "snap", ioctx, clone_name.c_str(), + features, &order)); + + librbd::Image clone_image; + ASSERT_EQ(0, rbd.open(ioctx, clone_image, clone_name.c_str(), NULL)); + + std::string clone_id; + ASSERT_EQ(0, clone_image.get_id(&clone_id)); + + std::vector<librbd::image_spec_t> images; + ASSERT_EQ(0, rbd.list2(ioctx, &images)); + + std::vector<librbd::image_spec_t> expected_images{ + {.id = parent_id, .name = name}, + {.id = clone_id, .name = clone_name} + }; + std::sort(expected_images.begin(), expected_images.end(), + [](const librbd::image_spec_t& lhs, const librbd::image_spec_t &rhs) { + return lhs.name < rhs.name; + }); + ASSERT_EQ(expected_images, images); + + librbd::linked_image_spec_t parent_image_spec; + librbd::snap_spec_t parent_snap_spec; + ASSERT_EQ(0, clone_image.get_parent(&parent_image_spec, &parent_snap_spec)); + + librbd::linked_image_spec_t expected_parent_image_spec{ + .pool_id = ioctx.get_id(), + .pool_name = ioctx.get_pool_name(), + .pool_namespace = ioctx.get_namespace(), + .image_id = parent_id, + .image_name = name, + .trash = false + }; + ASSERT_EQ(expected_parent_image_spec, parent_image_spec); + ASSERT_EQ(RBD_SNAP_NAMESPACE_TYPE_USER, parent_snap_spec.namespace_type); + ASSERT_EQ("snap", parent_snap_spec.name); + + std::vector<librbd::linked_image_spec_t> children; + ASSERT_EQ(0, parent_image.list_children3(&children)); + + std::vector<librbd::linked_image_spec_t> expected_children{ + { + .pool_id = ioctx.get_id(), + .pool_name = ioctx.get_pool_name(), + .pool_namespace = ioctx.get_namespace(), + .image_id = clone_id, + .image_name = clone_name, + .trash = false + } + }; + ASSERT_EQ(expected_children, children); + + children.clear(); + ASSERT_EQ(0, parent_image.list_descendants(&children)); + ASSERT_EQ(expected_children, children); + + ASSERT_EQ(0, clone_image.snap_create("snap")); + ASSERT_EQ(0, clone_image.snap_protect("snap")); + + auto grand_clone_name = this->get_temp_image_name(); + ASSERT_EQ(0, rbd.clone(ioctx, clone_name.c_str(), "snap", ioctx, + grand_clone_name.c_str(), features, &order)); + librbd::Image grand_clone_image; + ASSERT_EQ(0, rbd.open(ioctx, grand_clone_image, grand_clone_name.c_str(), + nullptr)); + std::string grand_clone_id; + ASSERT_EQ(0, grand_clone_image.get_id(&grand_clone_id)); + + children.clear(); + ASSERT_EQ(0, parent_image.list_children3(&children)); + ASSERT_EQ(expected_children, children); + + children.clear(); + ASSERT_EQ(0, parent_image.list_descendants(&children)); + expected_children.push_back( + { + .pool_id = ioctx.get_id(), + .pool_name = ioctx.get_pool_name(), + .pool_namespace = ioctx.get_namespace(), + .image_id = grand_clone_id, + .image_name = grand_clone_name, + .trash = false + } + ); + ASSERT_EQ(expected_children, children); +} + +TEST_F(TestLibRBD, SnapRemoveWithChildMissing) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + ASSERT_EQ(0, rados_conf_set(_cluster, "rbd_default_clone_format", "2")); + BOOST_SCOPE_EXIT_ALL(&) { + ASSERT_EQ(0, rados_conf_set(_cluster, "rbd_default_clone_format", "auto")); + }; + + librbd::RBD rbd; + rados_ioctx_t ioctx1, ioctx2; + string pool_name1 = create_pool(true); + rados_ioctx_create(_cluster, pool_name1.c_str(), &ioctx1); + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx2)); + + bool old_format; + uint64_t features; + rbd_image_t parent, child1, child2, child3; + int order = 0; + char child_id1[4096]; + char child_id2[4096]; + char child_id3[4096]; + + ASSERT_EQ(0, get_features(&old_format, &features)); + ASSERT_FALSE(old_format); + std::string parent_name = get_temp_image_name(); + std::string child_name1 = get_temp_image_name(); + std::string child_name2 = get_temp_image_name(); + std::string child_name3 = get_temp_image_name(); + ASSERT_EQ(0, create_image_full(ioctx1, parent_name.c_str(), 4<<20, &order, + false, features)); + ASSERT_EQ(0, rbd_open(ioctx1, parent_name.c_str(), &parent, NULL)); + ASSERT_EQ(0, rbd_snap_create(parent, "snap1")); + ASSERT_EQ(0, rbd_snap_create(parent, "snap2")); + + ASSERT_EQ(0, clone_image(ioctx1, parent, parent_name.c_str(), "snap1", + ioctx2, child_name1.c_str(), features, &order)); + ASSERT_EQ(0, clone_image(ioctx1, parent, parent_name.c_str(), "snap2", + ioctx1, child_name2.c_str(), features, &order)); + ASSERT_EQ(0, clone_image(ioctx1, parent, parent_name.c_str(), "snap2", + ioctx2, child_name3.c_str(), features, &order)); + + ASSERT_EQ(0, rbd_open(ioctx2, child_name1.c_str(), &child1, NULL)); + ASSERT_EQ(0, rbd_open(ioctx1, child_name2.c_str(), &child2, NULL)); + ASSERT_EQ(0, rbd_open(ioctx2, child_name3.c_str(), &child3, NULL)); + ASSERT_EQ(0, rbd_get_id(child1, child_id1, sizeof(child_id1))); + ASSERT_EQ(0, rbd_get_id(child2, child_id2, sizeof(child_id2))); + ASSERT_EQ(0, rbd_get_id(child3, child_id3, sizeof(child_id3))); + test_list_children2(parent, 3, + child_id1, m_pool_name.c_str(), child_name1.c_str(), false, + child_id2, pool_name1.c_str(), child_name2.c_str(), false, + child_id3, m_pool_name.c_str(), child_name3.c_str(), false); + + size_t max_size = 10; + rbd_linked_image_spec_t children[max_size]; + ASSERT_EQ(0, rbd_list_children3(parent, children, &max_size)); + ASSERT_EQ(3, static_cast<int>(max_size)); + rbd_linked_image_spec_list_cleanup(children, max_size); + + ASSERT_EQ(0, rbd_close(child1)); + ASSERT_EQ(0, rbd_close(child2)); + ASSERT_EQ(0, rbd_close(child3)); + rados_ioctx_destroy(ioctx2); + ASSERT_EQ(0, rados_pool_delete(_cluster, m_pool_name.c_str())); + _pool_names.erase(std::remove(_pool_names.begin(), + _pool_names.end(), m_pool_name), + _pool_names.end()); + EXPECT_EQ(0, rados_wait_for_latest_osdmap(_cluster)); + + ASSERT_EQ(0, rbd_list_children3(parent, children, &max_size)); + ASSERT_EQ(3, static_cast<int>(max_size)); + rbd_linked_image_spec_list_cleanup(children, max_size); + ASSERT_EQ(0, rbd_snap_remove(parent, "snap1")); + ASSERT_EQ(0, rbd_list_children3(parent, children, &max_size)); + ASSERT_EQ(2, static_cast<int>(max_size)); + rbd_linked_image_spec_list_cleanup(children, max_size); + + ASSERT_EQ(0, rbd_remove(ioctx1, child_name2.c_str())); + ASSERT_EQ(0, rbd_list_children3(parent, children, &max_size)); + ASSERT_EQ(1, static_cast<int>(max_size)); + rbd_linked_image_spec_list_cleanup(children, max_size); + + ASSERT_EQ(0, rbd_snap_remove(parent, "snap2")); + ASSERT_EQ(0, rbd_list_children3(parent, children, &max_size)); + ASSERT_EQ(0, static_cast<int>(max_size)); + rbd_linked_image_spec_list_cleanup(children, max_size); + test_list_children2(parent, 0); + ASSERT_EQ(0, test_ls_snaps(parent, 0)); + + ASSERT_EQ(0, rbd_close(parent)); + rados_ioctx_destroy(ioctx1); +} + +TEST_F(TestLibRBD, WriteZeroes) { + librbd::RBD rbd; + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + std::string name = get_temp_image_name(); + int order = 0; + uint64_t size = 2 << 20; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + // 1s from [0, 256) / length 256 + char data[256]; + memset(data, 1, sizeof(data)); + bufferlist bl; + bl.append(data, 256); + ASSERT_EQ(256, image.write(0, 256, bl)); + + interval_set<uint64_t> diff; + ASSERT_EQ(0, image.diff_iterate2(nullptr, 0, size, false, false, + iterate_cb, (void *)&diff)); + interval_set<uint64_t> expected_diff; + expected_diff.insert(0, 256); + ASSERT_EQ(expected_diff, diff); + + // writes zero passed the current end extents. + // Now 1s from [0, 192) / length 192 + ASSERT_EQ(static_cast<int64_t>(size - 192), + image.write_zeroes(192, size - 192, 0U, 0)); + diff.clear(); + ASSERT_EQ(0, image.diff_iterate2(nullptr, 0, size, false, false, + iterate_cb, (void *)&diff)); + expected_diff.clear(); + expected_diff.insert(0, 192); + ASSERT_EQ(expected_diff, diff); + + // zero an existing extent and truncate some off the end + // Now 1s from [64, 192) / length 192 + ASSERT_EQ(64, image.write_zeroes(0, 64, 0U, 0)); + + diff.clear(); + ASSERT_EQ(0, image.diff_iterate2(nullptr, 0, size, false, false, + iterate_cb, (void *)&diff)); + expected_diff.clear(); + expected_diff.insert(0, 192); + ASSERT_EQ(expected_diff, diff); + + bufferlist expected_bl; + expected_bl.append_zero(64); + bufferlist sub_bl; + sub_bl.substr_of(bl, 0, 128); + expected_bl.claim_append(sub_bl); + expected_bl.append_zero(size - 192); + + bufferlist read_bl; + EXPECT_EQ(static_cast<int64_t>(size), image.read(0, size, read_bl)); + EXPECT_EQ(expected_bl, read_bl); + + ASSERT_EQ(0, image.close()); +} + +// poorman's ceph_assert() +namespace ceph { + void __ceph_assert_fail(const char *assertion, const char *file, int line, + const char *func) { + ceph_abort(); + } +} + +#pragma GCC diagnostic pop +#pragma GCC diagnostic warning "-Wpragmas" diff --git a/src/test/librbd/test_main.cc b/src/test/librbd/test_main.cc new file mode 100644 index 00000000..4b66203b --- /dev/null +++ b/src/test/librbd/test_main.cc @@ -0,0 +1,72 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "include/rados/librados.hpp" +#include "global/global_context.h" +#include "test/librados/test.h" +#include "test/librados/test_cxx.h" +#include "gtest/gtest.h" +#include <iostream> +#include <string> + +extern void register_test_librbd(); +#ifdef TEST_LIBRBD_INTERNALS +extern void register_test_deep_copy(); +extern void register_test_groups(); +extern void register_test_image_watcher(); +extern void register_test_internal(); +extern void register_test_journal_entries(); +extern void register_test_journal_replay(); +extern void register_test_migration(); +extern void register_test_mirroring(); +extern void register_test_mirroring_watcher(); +extern void register_test_object_map(); +extern void register_test_operations(); +extern void register_test_trash(); +#endif // TEST_LIBRBD_INTERNALS + +int main(int argc, char **argv) +{ + setenv("RBD_FORCE_ALLOW_V1","1",1); + + register_test_librbd(); +#ifdef TEST_LIBRBD_INTERNALS + register_test_deep_copy(); + register_test_groups(); + register_test_image_watcher(); + register_test_internal(); + register_test_journal_entries(); + register_test_journal_replay(); + register_test_migration(); + register_test_mirroring(); + register_test_mirroring_watcher(); + register_test_object_map(); + register_test_operations(); + register_test_trash(); +#endif // TEST_LIBRBD_INTERNALS + + ::testing::InitGoogleTest(&argc, argv); + + librados::Rados rados; + std::string result = connect_cluster_pp(rados); + if (result != "" ) { + std::cerr << result << std::endl; + return 1; + } + +#ifdef TEST_LIBRBD_INTERNALS + g_ceph_context = reinterpret_cast<CephContext*>(rados.cct()); +#endif // TEST_LIBRBD_INTERNALS + + int r = rados.conf_set("lockdep", "true"); + if (r < 0) { + std::cerr << "failed to enable lockdep" << std::endl; + return -r; + } + + int seed = getpid(); + std::cout << "seed " << seed << std::endl; + srand(seed); + + return RUN_ALL_TESTS(); +} diff --git a/src/test/librbd/test_mirroring.cc b/src/test/librbd/test_mirroring.cc new file mode 100644 index 00000000..73c5479b --- /dev/null +++ b/src/test/librbd/test_mirroring.cc @@ -0,0 +1,953 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2016 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageState.h" +#include "librbd/ImageWatcher.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/Operations.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/io/ImageRequest.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/journal/Types.h" +#include "librbd/api/Image.h" +#include "journal/Journaler.h" +#include "journal/Settings.h" +#include <boost/scope_exit.hpp> +#include <boost/assign/list_of.hpp> +#include <utility> +#include <vector> + +void register_test_mirroring() { +} + +class TestMirroring : public TestFixture { +public: + + TestMirroring() {} + + + void TearDown() override { + unlock_image(); + + TestFixture::TearDown(); + } + + void SetUp() override { + ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), m_ioctx)); + } + + std::string image_name = "mirrorimg1"; + + void check_mirror_image_enable(rbd_mirror_mode_t mirror_mode, + uint64_t features, + int expected_r, + rbd_mirror_image_state_t mirror_state) { + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); + + int order = 20; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features, &order)); + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str())); + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, mirror_mode)); + + ASSERT_EQ(expected_r, image.mirror_image_enable()); + + librbd::mirror_image_info_t mirror_image; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image))); + ASSERT_EQ(mirror_state, mirror_image.state); + + librbd::mirror_image_status_t status; + ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status))); + ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state); + + std::string instance_id; + ASSERT_EQ(mirror_state == RBD_MIRROR_IMAGE_ENABLED ? -ENOENT : -EINVAL, + image.mirror_image_get_instance_id(&instance_id)); + + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str())); + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); + } + + void check_mirror_image_disable(rbd_mirror_mode_t mirror_mode, + uint64_t features, + int expected_r, + rbd_mirror_image_state_t mirror_state) { + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_POOL)); + + int order = 20; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features, &order)); + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str())); + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, mirror_mode)); + + ASSERT_EQ(expected_r, image.mirror_image_disable(false)); + + librbd::mirror_image_info_t mirror_image; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image))); + ASSERT_EQ(mirror_state, mirror_image.state); + + librbd::mirror_image_status_t status; + ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status))); + ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state); + + std::string instance_id; + ASSERT_EQ(mirror_state == RBD_MIRROR_IMAGE_ENABLED ? -ENOENT : -EINVAL, + image.mirror_image_get_instance_id(&instance_id)); + + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str())); + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); + } + + void check_mirroring_status(size_t *images_count) { + std::map<std::string, librbd::mirror_image_status_t> images; + ASSERT_EQ(0, m_rbd.mirror_image_status_list(m_ioctx, "", 4096, &images)); + + std::map<librbd::mirror_image_status_state_t, int> states; + ASSERT_EQ(0, m_rbd.mirror_image_status_summary(m_ioctx, &states)); + size_t states_count = 0; + for (auto &s : states) { + states_count += s.second; + } + ASSERT_EQ(images.size(), states_count); + + *images_count = images.size(); + + std::map<std::string, std::string> instance_ids; + ASSERT_EQ(0, m_rbd.mirror_image_instance_id_list(m_ioctx, "", 4096, + &instance_ids)); + ASSERT_TRUE(instance_ids.empty()); + } + + void check_mirroring_on_create(uint64_t features, + rbd_mirror_mode_t mirror_mode, + rbd_mirror_image_state_t mirror_state) { + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, mirror_mode)); + + size_t mirror_images_count = 0; + check_mirroring_status(&mirror_images_count); + + int order = 20; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features, &order)); + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str())); + + librbd::mirror_image_info_t mirror_image; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image))); + ASSERT_EQ(mirror_state, mirror_image.state); + + librbd::mirror_image_status_t status; + ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status))); + ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state); + + size_t mirror_images_new_count = 0; + check_mirroring_status(&mirror_images_new_count); + if (mirror_mode == RBD_MIRROR_MODE_POOL && + mirror_state == RBD_MIRROR_IMAGE_ENABLED) { + ASSERT_EQ(mirror_images_new_count, mirror_images_count + 1); + } else { + ASSERT_EQ(mirror_images_new_count, mirror_images_count); + } + + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str())); + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); + + check_mirroring_status(&mirror_images_new_count); + ASSERT_EQ(mirror_images_new_count, mirror_images_count); + } + + void check_mirroring_on_update_features(uint64_t init_features, + bool enable, bool enable_mirroring, + uint64_t features, int expected_r, + rbd_mirror_mode_t mirror_mode, + rbd_mirror_image_state_t mirror_state) { + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, mirror_mode)); + + int order = 20; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, init_features, &order)); + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str())); + + if (enable_mirroring) { + ASSERT_EQ(0, image.mirror_image_enable()); + } + + ASSERT_EQ(expected_r, image.update_features(features, enable)); + + librbd::mirror_image_info_t mirror_image; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image))); + ASSERT_EQ(mirror_state, mirror_image.state); + + librbd::mirror_image_status_t status; + ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status))); + ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state); + + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str())); + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); + } + + void setup_images_with_mirror_mode(rbd_mirror_mode_t mirror_mode, + std::vector<uint64_t>& features_vec) { + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, mirror_mode)); + + int id = 1; + int order = 20; + for (const auto& features : features_vec) { + std::stringstream img_name("img_"); + img_name << id++; + std::string img_name_str = img_name.str(); + ASSERT_EQ(0, m_rbd.create2(m_ioctx, img_name_str.c_str(), 2048, features, &order)); + } + } + + void check_mirroring_on_mirror_mode_set(rbd_mirror_mode_t mirror_mode, + std::vector<rbd_mirror_image_state_t>& states_vec) { + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, mirror_mode)); + + std::vector< std::tuple<std::string, rbd_mirror_image_state_t> > images; + int id = 1; + for (const auto& mirror_state : states_vec) { + std::stringstream img_name("img_"); + img_name << id++; + std::string img_name_str = img_name.str(); + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, img_name_str.c_str())); + images.push_back(std::make_tuple(img_name_str, mirror_state)); + + librbd::mirror_image_info_t mirror_image; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image))); + ASSERT_EQ(mirror_state, mirror_image.state); + + librbd::mirror_image_status_t status; + ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status))); + ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state); + + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, m_rbd.remove(m_ioctx, img_name_str.c_str())); + } + } + + void check_remove_image(rbd_mirror_mode_t mirror_mode, uint64_t features, + bool enable_mirroring, bool demote = false) { + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, mirror_mode)); + + int order = 20; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features, + &order)); + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str())); + + if (enable_mirroring) { + ASSERT_EQ(0, image.mirror_image_enable()); + } + + if (demote) { + ASSERT_EQ(0, image.mirror_image_demote()); + ASSERT_EQ(0, image.mirror_image_disable(true)); + } + + image.close(); + ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str())); + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); + } + + void check_trash_move_restore(rbd_mirror_mode_t mirror_mode, + bool enable_mirroring) { + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, mirror_mode)); + + int order = 20; + uint64_t features = RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features, + &order)); + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str())); + + if (enable_mirroring) { + ASSERT_EQ(0, image.mirror_image_enable()); + } + + std::string image_id; + ASSERT_EQ(0, image.get_id(&image_id)); + image.close(); + + ASSERT_EQ(0, m_rbd.trash_move(m_ioctx, image_name.c_str(), 100)); + + ASSERT_EQ(0, m_rbd.open_by_id(m_ioctx, image, image_id.c_str(), NULL)); + + librbd::mirror_image_info_t mirror_image; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image))); + ASSERT_EQ(mirror_image.state, RBD_MIRROR_IMAGE_DISABLED); + + ASSERT_EQ(0, m_rbd.trash_restore(m_ioctx, image_id.c_str(), "")); + + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, sizeof(mirror_image))); + if (mirror_mode == RBD_MIRROR_MODE_POOL) { + ASSERT_EQ(mirror_image.state, RBD_MIRROR_IMAGE_ENABLED); + } else { + ASSERT_EQ(mirror_image.state, RBD_MIRROR_IMAGE_DISABLED); + } + + image.close(); + ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str())); + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); + } + + void setup_mirror_peer(librados::IoCtx &io_ctx, librbd::Image &image) { + ASSERT_EQ(0, image.snap_create("sync-point-snap")); + + std::string image_id; + ASSERT_EQ(0, get_image_id(image, &image_id)); + + librbd::journal::MirrorPeerClientMeta peer_client_meta( + "remote-image-id", {{{}, "sync-point-snap", boost::none}}, {}); + librbd::journal::ClientData client_data(peer_client_meta); + + journal::Journaler journaler(io_ctx, image_id, "peer-client", {}); + C_SaferCond init_ctx; + journaler.init(&init_ctx); + ASSERT_EQ(-ENOENT, init_ctx.wait()); + + bufferlist client_data_bl; + encode(client_data, client_data_bl); + ASSERT_EQ(0, journaler.register_client(client_data_bl)); + + C_SaferCond shut_down_ctx; + journaler.shut_down(&shut_down_ctx); + ASSERT_EQ(0, shut_down_ctx.wait()); + } + +}; + +TEST_F(TestMirroring, EnableImageMirror_In_MirrorModeImage) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + features |= RBD_FEATURE_JOURNALING; + check_mirror_image_enable(RBD_MIRROR_MODE_IMAGE, features, 0, + RBD_MIRROR_IMAGE_ENABLED); +} + +TEST_F(TestMirroring, EnableImageMirror_In_MirrorModePool) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + features |= RBD_FEATURE_JOURNALING; + check_mirror_image_enable(RBD_MIRROR_MODE_POOL, features, -EINVAL, + RBD_MIRROR_IMAGE_ENABLED); +} + +TEST_F(TestMirroring, EnableImageMirror_In_MirrorModeDisabled) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + features |= RBD_FEATURE_JOURNALING; + check_mirror_image_enable(RBD_MIRROR_MODE_DISABLED, features, -EINVAL, + RBD_MIRROR_IMAGE_DISABLED); +} + +TEST_F(TestMirroring, DisableImageMirror_In_MirrorModeImage) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + features |= RBD_FEATURE_JOURNALING; + check_mirror_image_disable(RBD_MIRROR_MODE_IMAGE, features, 0, + RBD_MIRROR_IMAGE_DISABLED); +} + +TEST_F(TestMirroring, DisableImageMirror_In_MirrorModePool) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + features |= RBD_FEATURE_JOURNALING; + check_mirror_image_disable(RBD_MIRROR_MODE_POOL, features, -EINVAL, + RBD_MIRROR_IMAGE_ENABLED); +} + +TEST_F(TestMirroring, DisableImageMirror_In_MirrorModeDisabled) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + features |= RBD_FEATURE_JOURNALING; + check_mirror_image_disable(RBD_MIRROR_MODE_DISABLED, features, -EINVAL, + RBD_MIRROR_IMAGE_DISABLED); +} + +TEST_F(TestMirroring, DisableImageMirrorWithPeer) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE)); + + uint64_t features = RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING; + int order = 20; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features, + &order)); + + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str())); + ASSERT_EQ(0, image.mirror_image_enable()); + + setup_mirror_peer(m_ioctx, image); + + ASSERT_EQ(0, image.mirror_image_disable(false)); + + std::vector<librbd::snap_info_t> snaps; + ASSERT_EQ(0, image.snap_list(snaps)); + ASSERT_TRUE(snaps.empty()); + + librbd::mirror_image_info_t mirror_image; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, + sizeof(mirror_image))); + ASSERT_EQ(RBD_MIRROR_IMAGE_DISABLED, mirror_image.state); + + librbd::mirror_image_status_t status; + ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status))); + ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state); + + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str())); + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); +} + +TEST_F(TestMirroring, DisableJournalingWithPeer) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_POOL)); + + uint64_t features = RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING; + int order = 20; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features, + &order)); + + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str())); + + setup_mirror_peer(m_ioctx, image); + + ASSERT_EQ(0, image.update_features(RBD_FEATURE_JOURNALING, false)); + + std::vector<librbd::snap_info_t> snaps; + ASSERT_EQ(0, image.snap_list(snaps)); + ASSERT_TRUE(snaps.empty()); + + librbd::mirror_image_info_t mirror_image; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, + sizeof(mirror_image))); + ASSERT_EQ(RBD_MIRROR_IMAGE_DISABLED, mirror_image.state); + + librbd::mirror_image_status_t status; + ASSERT_EQ(0, image.mirror_image_get_status(&status, sizeof(status))); + ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state); + + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str())); + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); +} + +TEST_F(TestMirroring, EnableImageMirror_WithoutJournaling) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + check_mirror_image_enable(RBD_MIRROR_MODE_DISABLED, features, -EINVAL, + RBD_MIRROR_IMAGE_DISABLED); +} + +TEST_F(TestMirroring, CreateImage_In_MirrorModeDisabled) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + features |= RBD_FEATURE_JOURNALING; + check_mirroring_on_create(features, RBD_MIRROR_MODE_DISABLED, + RBD_MIRROR_IMAGE_DISABLED); +} + +TEST_F(TestMirroring, CreateImage_In_MirrorModeImage) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + features |= RBD_FEATURE_JOURNALING; + check_mirroring_on_create(features, RBD_MIRROR_MODE_IMAGE, + RBD_MIRROR_IMAGE_DISABLED); +} + +TEST_F(TestMirroring, CreateImage_In_MirrorModePool) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + features |= RBD_FEATURE_JOURNALING; + check_mirroring_on_create(features, RBD_MIRROR_MODE_POOL, + RBD_MIRROR_IMAGE_ENABLED); +} + +TEST_F(TestMirroring, CreateImage_In_MirrorModePool_WithoutJournaling) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + check_mirroring_on_create(features, RBD_MIRROR_MODE_POOL, + RBD_MIRROR_IMAGE_DISABLED); +} + +TEST_F(TestMirroring, CreateImage_In_MirrorModeImage_WithoutJournaling) { + uint64_t features = 0; + features |= RBD_FEATURE_OBJECT_MAP; + features |= RBD_FEATURE_EXCLUSIVE_LOCK; + check_mirroring_on_create(features, RBD_MIRROR_MODE_IMAGE, + RBD_MIRROR_IMAGE_DISABLED); +} + +TEST_F(TestMirroring, EnableJournaling_In_MirrorModeDisabled) { + uint64_t init_features = 0; + init_features |= RBD_FEATURE_OBJECT_MAP; + init_features |= RBD_FEATURE_EXCLUSIVE_LOCK; + uint64_t features = RBD_FEATURE_JOURNALING; + check_mirroring_on_update_features(init_features, true, false, features, 0, + RBD_MIRROR_MODE_DISABLED, RBD_MIRROR_IMAGE_DISABLED); +} + +TEST_F(TestMirroring, EnableJournaling_In_MirrorModeImage) { + uint64_t init_features = 0; + init_features |= RBD_FEATURE_OBJECT_MAP; + init_features |= RBD_FEATURE_EXCLUSIVE_LOCK; + uint64_t features = RBD_FEATURE_JOURNALING; + check_mirroring_on_update_features(init_features, true, false, features, 0, + RBD_MIRROR_MODE_IMAGE, RBD_MIRROR_IMAGE_DISABLED); +} + +TEST_F(TestMirroring, EnableJournaling_In_MirrorModePool) { + uint64_t init_features = 0; + init_features |= RBD_FEATURE_OBJECT_MAP; + init_features |= RBD_FEATURE_EXCLUSIVE_LOCK; + uint64_t features = RBD_FEATURE_JOURNALING; + check_mirroring_on_update_features(init_features, true, false, features, 0, + RBD_MIRROR_MODE_POOL, RBD_MIRROR_IMAGE_ENABLED); +} + +TEST_F(TestMirroring, DisableJournaling_In_MirrorModePool) { + uint64_t init_features = 0; + init_features |= RBD_FEATURE_OBJECT_MAP; + init_features |= RBD_FEATURE_EXCLUSIVE_LOCK; + init_features |= RBD_FEATURE_JOURNALING; + uint64_t features = RBD_FEATURE_JOURNALING; + check_mirroring_on_update_features(init_features, false, false, features, 0, + RBD_MIRROR_MODE_POOL, RBD_MIRROR_IMAGE_DISABLED); +} + +TEST_F(TestMirroring, DisableJournaling_In_MirrorModeImage) { + uint64_t init_features = 0; + init_features |= RBD_FEATURE_OBJECT_MAP; + init_features |= RBD_FEATURE_EXCLUSIVE_LOCK; + init_features |= RBD_FEATURE_JOURNALING; + uint64_t features = RBD_FEATURE_JOURNALING; + check_mirroring_on_update_features(init_features, false, true, features, -EINVAL, + RBD_MIRROR_MODE_IMAGE, RBD_MIRROR_IMAGE_ENABLED); +} + +TEST_F(TestMirroring, MirrorModeSet_DisabledMode_To_PoolMode) { + std::vector<uint64_t> features_vec; + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK); + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING); + + setup_images_with_mirror_mode(RBD_MIRROR_MODE_DISABLED, features_vec); + + std::vector<rbd_mirror_image_state_t> states_vec; + states_vec.push_back(RBD_MIRROR_IMAGE_DISABLED); + states_vec.push_back(RBD_MIRROR_IMAGE_ENABLED); + check_mirroring_on_mirror_mode_set(RBD_MIRROR_MODE_POOL, states_vec); +} + +TEST_F(TestMirroring, MirrorModeSet_PoolMode_To_DisabledMode) { + std::vector<uint64_t> features_vec; + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK); + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING); + + setup_images_with_mirror_mode(RBD_MIRROR_MODE_POOL, features_vec); + + std::vector<rbd_mirror_image_state_t> states_vec; + states_vec.push_back(RBD_MIRROR_IMAGE_DISABLED); + states_vec.push_back(RBD_MIRROR_IMAGE_DISABLED); + check_mirroring_on_mirror_mode_set(RBD_MIRROR_MODE_DISABLED, states_vec); +} + +TEST_F(TestMirroring, MirrorModeSet_DisabledMode_To_ImageMode) { + std::vector<uint64_t> features_vec; + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK); + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING); + + setup_images_with_mirror_mode(RBD_MIRROR_MODE_DISABLED, features_vec); + + std::vector<rbd_mirror_image_state_t> states_vec; + states_vec.push_back(RBD_MIRROR_IMAGE_DISABLED); + states_vec.push_back(RBD_MIRROR_IMAGE_DISABLED); + check_mirroring_on_mirror_mode_set(RBD_MIRROR_MODE_IMAGE, states_vec); +} + + +TEST_F(TestMirroring, MirrorModeSet_PoolMode_To_ImageMode) { + std::vector<uint64_t> features_vec; + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK); + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING); + + setup_images_with_mirror_mode(RBD_MIRROR_MODE_POOL, features_vec); + + std::vector<rbd_mirror_image_state_t> states_vec; + states_vec.push_back(RBD_MIRROR_IMAGE_DISABLED); + states_vec.push_back(RBD_MIRROR_IMAGE_ENABLED); + check_mirroring_on_mirror_mode_set(RBD_MIRROR_MODE_IMAGE, states_vec); +} + +TEST_F(TestMirroring, MirrorModeSet_ImageMode_To_PoolMode) { + std::vector<uint64_t> features_vec; + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK); + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING); + + setup_images_with_mirror_mode(RBD_MIRROR_MODE_IMAGE, features_vec); + + std::vector<rbd_mirror_image_state_t> states_vec; + states_vec.push_back(RBD_MIRROR_IMAGE_DISABLED); + states_vec.push_back(RBD_MIRROR_IMAGE_ENABLED); + check_mirroring_on_mirror_mode_set(RBD_MIRROR_MODE_POOL, states_vec); +} + +TEST_F(TestMirroring, MirrorModeSet_ImageMode_To_DisabledMode) { + std::vector<uint64_t> features_vec; + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK); + features_vec.push_back(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING); + + setup_images_with_mirror_mode(RBD_MIRROR_MODE_POOL, features_vec); + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE)); + ASSERT_EQ(-EINVAL, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_POOL)); + + std::vector<rbd_mirror_image_state_t> states_vec; + states_vec.push_back(RBD_MIRROR_IMAGE_DISABLED); + states_vec.push_back(RBD_MIRROR_IMAGE_DISABLED); + check_mirroring_on_mirror_mode_set(RBD_MIRROR_MODE_DISABLED, states_vec); +} + +TEST_F(TestMirroring, RemoveImage_With_MirrorImageEnabled) { + check_remove_image(RBD_MIRROR_MODE_IMAGE, + RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING, + true); +} + +TEST_F(TestMirroring, RemoveImage_With_MirrorImageDisabled) { + check_remove_image(RBD_MIRROR_MODE_IMAGE, + RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING, + false); +} + +TEST_F(TestMirroring, RemoveImage_With_ImageWithoutJournal) { + check_remove_image(RBD_MIRROR_MODE_IMAGE, + RBD_FEATURE_EXCLUSIVE_LOCK, + false); +} + +TEST_F(TestMirroring, RemoveImage_With_MirrorImageDemoted) { + check_remove_image(RBD_MIRROR_MODE_IMAGE, + RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING, + true, true); +} + +TEST_F(TestMirroring, TrashMoveRestore_PoolMode) { + check_trash_move_restore(RBD_MIRROR_MODE_POOL, false); +} + +TEST_F(TestMirroring, TrashMoveRestore_ImageMode_MirroringDisabled) { + check_trash_move_restore(RBD_MIRROR_MODE_IMAGE, false); +} + +TEST_F(TestMirroring, TrashMoveRestore_ImageMode_MirroringEnabled) { + check_trash_move_restore(RBD_MIRROR_MODE_IMAGE, true); +} + +TEST_F(TestMirroring, MirrorStatusList) { + std::vector<uint64_t> + features_vec(5, RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING); + setup_images_with_mirror_mode(RBD_MIRROR_MODE_POOL, features_vec); + + std::string last_read = ""; + std::map<std::string, librbd::mirror_image_status_t> images; + ASSERT_EQ(0, m_rbd.mirror_image_status_list(m_ioctx, last_read, 2, &images)); + ASSERT_EQ(2U, images.size()); + + last_read = images.rbegin()->first; + images.clear(); + ASSERT_EQ(0, m_rbd.mirror_image_status_list(m_ioctx, last_read, 2, &images)); + ASSERT_EQ(2U, images.size()); + + last_read = images.rbegin()->first; + images.clear(); + ASSERT_EQ(0, m_rbd.mirror_image_status_list(m_ioctx, last_read, 4096, &images)); + ASSERT_EQ(1U, images.size()); + + last_read = images.rbegin()->first; + images.clear(); + ASSERT_EQ(0, m_rbd.mirror_image_status_list(m_ioctx, last_read, 4096, &images)); + ASSERT_EQ(0U, images.size()); +} + +TEST_F(TestMirroring, RemoveBootstrapped) +{ + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_POOL)); + + uint64_t features = RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING; + int order = 20; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features, + &order)); + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str())); + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(-EBUSY, librbd::api::Image<>::remove(m_ioctx, image_name, no_op)); + + // simulate the image is open by rbd-mirror bootstrap + uint64_t handle; + struct MirrorWatcher : public librados::WatchCtx2 { + explicit MirrorWatcher(librados::IoCtx &ioctx) : m_ioctx(ioctx) { + } + void handle_notify(uint64_t notify_id, uint64_t cookie, + uint64_t notifier_id, bufferlist& bl) override { + // received IMAGE_UPDATED notification from remove + m_notified = true; + m_ioctx.notify_ack(RBD_MIRRORING, notify_id, cookie, bl); + } + void handle_error(uint64_t cookie, int err) override { + } + librados::IoCtx &m_ioctx; + bool m_notified = false; + } watcher(m_ioctx); + ASSERT_EQ(0, m_ioctx.create(RBD_MIRRORING, false)); + ASSERT_EQ(0, m_ioctx.watch2(RBD_MIRRORING, &handle, &watcher)); + // now remove should succeed + ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, image_name, no_op)); + ASSERT_EQ(0, m_ioctx.unwatch2(handle)); + ASSERT_TRUE(watcher.m_notified); + ASSERT_EQ(0, image.close()); +} + +TEST_F(TestMirroring, AioPromoteDemote) { + std::list<std::string> image_names; + for (size_t idx = 0; idx < 10; ++idx) { + image_names.push_back(get_temp_image_name()); + } + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE)); + + // create mirror images + int order = 20; + std::list<librbd::Image> images; + for (auto &image_name : image_names) { + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 2048, + RBD_FEATURE_EXCLUSIVE_LOCK | + RBD_FEATURE_JOURNALING, + &order)); + + images.emplace_back(); + ASSERT_EQ(0, m_rbd.open(m_ioctx, images.back(), image_name.c_str())); + ASSERT_EQ(0, images.back().mirror_image_enable()); + } + + // demote all images + std::list<librbd::RBD::AioCompletion *> aio_comps; + for (auto &image : images) { + aio_comps.push_back(new librbd::RBD::AioCompletion(nullptr, nullptr)); + ASSERT_EQ(0, image.aio_mirror_image_demote(aio_comps.back())); + } + for (auto aio_comp : aio_comps) { + ASSERT_EQ(0, aio_comp->wait_for_complete()); + ASSERT_EQ(1, aio_comp->is_complete()); + ASSERT_EQ(0, aio_comp->get_return_value()); + aio_comp->release(); + } + aio_comps.clear(); + + // verify demotions + for (auto &image : images) { + librbd::mirror_image_info_t mirror_image; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, + sizeof(mirror_image))); + ASSERT_FALSE(mirror_image.primary); + } + + // promote all images + for (auto &image : images) { + aio_comps.push_back(new librbd::RBD::AioCompletion(nullptr, nullptr)); + ASSERT_EQ(0, image.aio_mirror_image_promote(false, aio_comps.back())); + } + for (auto aio_comp : aio_comps) { + ASSERT_EQ(0, aio_comp->wait_for_complete()); + ASSERT_EQ(1, aio_comp->is_complete()); + ASSERT_EQ(0, aio_comp->get_return_value()); + aio_comp->release(); + } + + // verify promotions + for (auto &image : images) { + librbd::mirror_image_info_t mirror_image; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image, + sizeof(mirror_image))); + ASSERT_TRUE(mirror_image.primary); + } +} + +TEST_F(TestMirroring, AioGetInfo) { + std::list<std::string> image_names; + for (size_t idx = 0; idx < 10; ++idx) { + image_names.push_back(get_temp_image_name()); + } + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_POOL)); + + // create mirror images + int order = 20; + std::list<librbd::Image> images; + for (auto &image_name : image_names) { + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 2048, + RBD_FEATURE_EXCLUSIVE_LOCK | + RBD_FEATURE_JOURNALING, + &order)); + + images.emplace_back(); + ASSERT_EQ(0, m_rbd.open(m_ioctx, images.back(), image_name.c_str())); + } + + std::list<librbd::RBD::AioCompletion *> aio_comps; + std::list<librbd::mirror_image_info_t> infos; + for (auto &image : images) { + aio_comps.push_back(new librbd::RBD::AioCompletion(nullptr, nullptr)); + infos.emplace_back(); + ASSERT_EQ(0, image.aio_mirror_image_get_info(&infos.back(), + sizeof(infos.back()), + aio_comps.back())); + } + for (auto aio_comp : aio_comps) { + ASSERT_EQ(0, aio_comp->wait_for_complete()); + ASSERT_EQ(1, aio_comp->is_complete()); + ASSERT_EQ(0, aio_comp->get_return_value()); + aio_comp->release(); + } + aio_comps.clear(); + + for (auto &info : infos) { + ASSERT_NE("", info.global_id); + ASSERT_EQ(RBD_MIRROR_IMAGE_ENABLED, info.state); + ASSERT_TRUE(info.primary); + } +} + +TEST_F(TestMirroring, AioGetStatus) { + std::list<std::string> image_names; + for (size_t idx = 0; idx < 10; ++idx) { + image_names.push_back(get_temp_image_name()); + } + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_POOL)); + + // create mirror images + int order = 20; + std::list<librbd::Image> images; + for (auto &image_name : image_names) { + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 2048, + RBD_FEATURE_EXCLUSIVE_LOCK | + RBD_FEATURE_JOURNALING, + &order)); + + images.emplace_back(); + ASSERT_EQ(0, m_rbd.open(m_ioctx, images.back(), image_name.c_str())); + } + + std::list<librbd::RBD::AioCompletion *> aio_comps; + std::list<librbd::mirror_image_status_t> statuses; + for (auto &image : images) { + aio_comps.push_back(new librbd::RBD::AioCompletion(nullptr, nullptr)); + statuses.emplace_back(); + ASSERT_EQ(0, image.aio_mirror_image_get_status(&statuses.back(), + sizeof(statuses.back()), + aio_comps.back())); + } + for (auto aio_comp : aio_comps) { + ASSERT_EQ(0, aio_comp->wait_for_complete()); + ASSERT_EQ(1, aio_comp->is_complete()); + ASSERT_EQ(0, aio_comp->get_return_value()); + aio_comp->release(); + } + aio_comps.clear(); + + for (auto &status : statuses) { + ASSERT_NE("", status.name); + ASSERT_NE("", status.info.global_id); + ASSERT_EQ(RBD_MIRROR_IMAGE_ENABLED, status.info.state); + ASSERT_TRUE(status.info.primary); + ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, status.state); + ASSERT_EQ("status not found", status.description); + ASSERT_FALSE(status.up); + ASSERT_EQ(0, status.last_update); + } +} + +TEST_F(TestMirroring, SiteName) { + REQUIRE(!is_librados_test_stub(_rados)); + + const std::string expected_site_name("us-east-1a"); + ASSERT_EQ(0, m_rbd.mirror_site_name_set(_rados, expected_site_name)); + + std::string site_name; + ASSERT_EQ(0, m_rbd.mirror_site_name_get(_rados, &site_name)); + ASSERT_EQ(expected_site_name, site_name); + + ASSERT_EQ(0, m_rbd.mirror_site_name_set(_rados, "")); + + std::string fsid; + ASSERT_EQ(0, _rados.cluster_fsid(&fsid)); + ASSERT_EQ(0, m_rbd.mirror_site_name_get(_rados, &site_name)); + ASSERT_EQ(fsid, site_name); +} + +TEST_F(TestMirroring, Bootstrap) { + REQUIRE(!is_librados_test_stub(_rados)); + + std::string token_b64; + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); + ASSERT_EQ(-EINVAL, m_rbd.mirror_peer_bootstrap_create(m_ioctx, &token_b64)); + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_POOL)); + ASSERT_EQ(0, m_rbd.mirror_peer_bootstrap_create(m_ioctx, &token_b64)); + + bufferlist token_b64_bl; + token_b64_bl.append(token_b64); + + bufferlist token_bl; + token_bl.decode_base64(token_b64_bl); + + // cannot import token into same cluster + ASSERT_EQ(-EINVAL, + m_rbd.mirror_peer_bootstrap_import( + m_ioctx, RBD_MIRROR_PEER_DIRECTION_RX, token_b64)); +} diff --git a/src/test/librbd/test_mock_ConfigWatcher.cc b/src/test/librbd/test_mock_ConfigWatcher.cc new file mode 100644 index 00000000..264e3d2c --- /dev/null +++ b/src/test/librbd/test_mock_ConfigWatcher.cc @@ -0,0 +1,100 @@ +// -*- 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 "include/rbd_types.h" +#include "common/ceph_mutex.h" +#include "librbd/ConfigWatcher.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include <list> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +#include "librbd/ConfigWatcher.cc" + +namespace librbd { + +using ::testing::Invoke; + +class TestMockConfigWatcher : public TestMockFixture { +public: + typedef ConfigWatcher<MockTestImageCtx> MockConfigWatcher; + + librbd::ImageCtx *m_image_ctx; + + ceph::mutex m_lock = ceph::make_mutex("m_lock"); + ceph::condition_variable m_cv; + bool m_refreshed = false; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx)); + } + + void expect_update_notification(MockTestImageCtx& mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, handle_update_notification()) + .WillOnce(Invoke([this]() { + std::unique_lock locker{m_lock}; + m_refreshed = true; + m_cv.notify_all(); + })); + } + + void wait_for_update_notification() { + std::unique_lock locker{m_lock}; + m_cv.wait(locker, [this] { + if (m_refreshed) { + m_refreshed = false; + return true; + } + return false; + }); + } +}; + +TEST_F(TestMockConfigWatcher, GlobalConfig) { + MockTestImageCtx mock_image_ctx(*m_image_ctx); + + MockConfigWatcher mock_config_watcher(mock_image_ctx); + mock_config_watcher.init(); + + expect_update_notification(mock_image_ctx); + mock_image_ctx.cct->_conf.set_val("rbd_cache", "false"); + mock_image_ctx.cct->_conf.set_val("rbd_cache", "true"); + mock_image_ctx.cct->_conf.apply_changes(nullptr); + wait_for_update_notification(); + + mock_config_watcher.shut_down(); +} + +TEST_F(TestMockConfigWatcher, IgnoreOverriddenGlobalConfig) { + MockTestImageCtx mock_image_ctx(*m_image_ctx); + + MockConfigWatcher mock_config_watcher(mock_image_ctx); + mock_config_watcher.init(); + + EXPECT_CALL(*mock_image_ctx.state, handle_update_notification()) + .Times(0); + mock_image_ctx.config_overrides.insert("rbd_cache"); + mock_image_ctx.cct->_conf.set_val("rbd_cache", "false"); + mock_image_ctx.cct->_conf.set_val("rbd_cache", "true"); + mock_image_ctx.cct->_conf.apply_changes(nullptr); + + mock_config_watcher.shut_down(); + + ASSERT_FALSE(m_refreshed); +} + +} // namespace librbd diff --git a/src/test/librbd/test_mock_DeepCopyRequest.cc b/src/test/librbd/test_mock_DeepCopyRequest.cc new file mode 100644 index 00000000..483c65aa --- /dev/null +++ b/src/test/librbd/test_mock_DeepCopyRequest.cc @@ -0,0 +1,457 @@ +// -*- 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 "include/rbd/librbd.hpp" +#include "librbd/DeepCopyRequest.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/internal.h" +#include "librbd/api/Image.h" +#include "librbd/deep_copy/ImageCopyRequest.h" +#include "librbd/deep_copy/MetadataCopyRequest.h" +#include "librbd/deep_copy/SnapshotCopyRequest.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockObjectMap.h" +#include "test/librbd/test_support.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace deep_copy { + +template <> +class ImageCopyRequest<librbd::MockTestImageCtx> { +public: + static ImageCopyRequest* s_instance; + Context *on_finish; + + static ImageCopyRequest* create( + librbd::MockTestImageCtx *src_image_ctx, + librbd::MockTestImageCtx *dst_image_ctx, + librados::snap_t src_snap_id_start, + librados::snap_t src_snap_id_end, + librados::snap_t dst_snap_id_start, + bool flatten, const ObjectNumber &object_number, + const SnapSeqs &snap_seqs, ProgressContext *prog_ctx, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + ImageCopyRequest() { + s_instance = this; + } + + void put() { + } + + void get() { + } + + MOCK_METHOD0(cancel, void()); + MOCK_METHOD0(send, void()); +}; + +template <> +class MetadataCopyRequest<librbd::MockTestImageCtx> { +public: + static MetadataCopyRequest* s_instance; + Context *on_finish; + + static MetadataCopyRequest* create(librbd::MockTestImageCtx *src_image_ctx, + librbd::MockTestImageCtx *dst_image_ctx, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MetadataCopyRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template <> +class SnapshotCopyRequest<librbd::MockTestImageCtx> { +public: + static SnapshotCopyRequest* s_instance; + Context *on_finish; + + static SnapshotCopyRequest* create(librbd::MockTestImageCtx *src_image_ctx, + librbd::MockTestImageCtx *dst_image_ctx, + librados::snap_t src_snap_id_start, + librados::snap_t src_snap_id_end, + librados::snap_t dst_snap_id_start, + bool flatten, ContextWQ *work_queue, + SnapSeqs *snap_seqs, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SnapshotCopyRequest() { + s_instance = this; + } + + void put() { + } + + void get() { + } + + MOCK_METHOD0(cancel, void()); + MOCK_METHOD0(send, void()); +}; + +ImageCopyRequest<librbd::MockTestImageCtx>* ImageCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr; +MetadataCopyRequest<librbd::MockTestImageCtx>* MetadataCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr; +SnapshotCopyRequest<librbd::MockTestImageCtx>* SnapshotCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace deep_copy +} // namespace librbd + +// template definitions +#include "librbd/DeepCopyRequest.cc" + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::ReturnNew; +using ::testing::SetArgPointee; +using ::testing::WithArg; + +class TestMockDeepCopyRequest : public TestMockFixture { +public: + typedef librbd::DeepCopyRequest<librbd::MockTestImageCtx> MockDeepCopyRequest; + typedef librbd::deep_copy::ImageCopyRequest<librbd::MockTestImageCtx> MockImageCopyRequest; + typedef librbd::deep_copy::MetadataCopyRequest<librbd::MockTestImageCtx> MockMetadataCopyRequest; + typedef librbd::deep_copy::SnapshotCopyRequest<librbd::MockTestImageCtx> MockSnapshotCopyRequest; + + librbd::ImageCtx *m_src_image_ctx; + librbd::ImageCtx *m_dst_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + void SetUp() override { + TestMockFixture::SetUp(); + + librbd::RBD rbd; + ASSERT_EQ(0, open_image(m_image_name, &m_src_image_ctx)); + + ASSERT_EQ(0, open_image(m_image_name, &m_dst_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_src_image_ctx->cct, + &m_thread_pool, &m_work_queue); + } + + void TearDown() override { + TestMockFixture::TearDown(); + } + + void expect_test_features(librbd::MockImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_, _)) + .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) { + return (mock_image_ctx.features & features) != 0; + }))); + } + + void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) { + EXPECT_CALL(mock_exclusive_lock, start_op(_)).WillOnce( + ReturnNew<FunctionContext>([](int) {})); + } + + void expect_rollback_object_map(librbd::MockObjectMap &mock_object_map, int r) { + EXPECT_CALL(mock_object_map, rollback(_, _)) + .WillOnce(WithArg<1>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + }))); + } + + void expect_create_object_map(librbd::MockTestImageCtx &mock_image_ctx, + librbd::MockObjectMap *mock_object_map) { + EXPECT_CALL(mock_image_ctx, create_object_map(CEPH_NOSNAP)) + .WillOnce(Return(mock_object_map)); + } + + void expect_open_object_map(librbd::MockTestImageCtx &mock_image_ctx, + librbd::MockObjectMap &mock_object_map, int r) { + EXPECT_CALL(mock_object_map, open(_)) + .WillOnce(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + })); + } + + void expect_copy_snapshots( + MockSnapshotCopyRequest &mock_snapshot_copy_request, int r) { + EXPECT_CALL(mock_snapshot_copy_request, send()) + .WillOnce(Invoke([this, &mock_snapshot_copy_request, r]() { + m_work_queue->queue(mock_snapshot_copy_request.on_finish, r); + })); + } + + void expect_copy_image(MockImageCopyRequest &mock_image_copy_request, int r) { + EXPECT_CALL(mock_image_copy_request, send()) + .WillOnce(Invoke([this, &mock_image_copy_request, r]() { + m_work_queue->queue(mock_image_copy_request.on_finish, r); + })); + } + + void expect_copy_object_map(librbd::MockExclusiveLock &mock_exclusive_lock, + librbd::MockObjectMap *mock_object_map, int r) { + expect_start_op(mock_exclusive_lock); + expect_rollback_object_map(*mock_object_map, r); + } + + void expect_refresh_object_map(librbd::MockTestImageCtx &mock_image_ctx, + librbd::MockObjectMap *mock_object_map, + int r) { + expect_start_op(*mock_image_ctx.exclusive_lock); + expect_create_object_map(mock_image_ctx, mock_object_map); + expect_open_object_map(mock_image_ctx, *mock_object_map, r); + } + + void expect_copy_metadata(MockMetadataCopyRequest &mock_metadata_copy_request, + int r) { + EXPECT_CALL(mock_metadata_copy_request, send()) + .WillOnce(Invoke([this, &mock_metadata_copy_request, r]() { + m_work_queue->queue(mock_metadata_copy_request.on_finish, r); + })); + } +}; + +TEST_F(TestMockDeepCopyRequest, SimpleCopy) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockImageCopyRequest mock_image_copy_request; + MockMetadataCopyRequest mock_metadata_copy_request; + MockSnapshotCopyRequest mock_snapshot_copy_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + mock_dst_image_ctx.exclusive_lock = &mock_exclusive_lock; + + librbd::MockObjectMap *mock_object_map = + is_feature_enabled(RBD_FEATURE_OBJECT_MAP) ? + new librbd::MockObjectMap() : nullptr; + mock_dst_image_ctx.object_map = mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, 0); + expect_copy_image(mock_image_copy_request, 0); + if (mock_object_map != nullptr) { + expect_refresh_object_map(mock_dst_image_ctx, mock_object_map, 0); + } + expect_copy_metadata(mock_metadata_copy_request, 0); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest<librbd::MockTestImageCtx>::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, CEPH_NOSNAP, 0, false, + boost::none, m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyRequest, ErrorOnCopySnapshots) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCopyRequest mock_snapshot_copy_request; + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, -EINVAL); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest<librbd::MockTestImageCtx>::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, CEPH_NOSNAP, 0, false, + boost::none, m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyRequest, ErrorOnRefreshObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockImageCopyRequest mock_image_copy_request; + MockSnapshotCopyRequest mock_snapshot_copy_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + mock_dst_image_ctx.exclusive_lock = &mock_exclusive_lock; + + librbd::MockObjectMap *mock_object_map = new librbd::MockObjectMap(); + mock_dst_image_ctx.object_map = mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, 0); + expect_copy_image(mock_image_copy_request, 0); + expect_start_op(*mock_dst_image_ctx.exclusive_lock); + expect_create_object_map(mock_dst_image_ctx, mock_object_map); + expect_open_object_map(mock_dst_image_ctx, *mock_object_map, -EINVAL); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest<librbd::MockTestImageCtx>::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, CEPH_NOSNAP, 0, false, + boost::none, m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyRequest, ErrorOnCopyImage) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockImageCopyRequest mock_image_copy_request; + MockSnapshotCopyRequest mock_snapshot_copy_request; + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, 0); + expect_copy_image(mock_image_copy_request, -EINVAL); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest<librbd::MockTestImageCtx>::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, CEPH_NOSNAP, 0, false, + boost::none, m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyRequest, ErrorOnCopyMetadata) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockImageCopyRequest mock_image_copy_request; + MockMetadataCopyRequest mock_metadata_copy_request; + MockSnapshotCopyRequest mock_snapshot_copy_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + mock_dst_image_ctx.exclusive_lock = &mock_exclusive_lock; + + librbd::MockObjectMap *mock_object_map = + is_feature_enabled(RBD_FEATURE_OBJECT_MAP) ? + new librbd::MockObjectMap() : nullptr; + mock_dst_image_ctx.object_map = mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, 0); + expect_copy_image(mock_image_copy_request, 0); + if (mock_object_map != nullptr) { + expect_refresh_object_map(mock_dst_image_ctx, mock_object_map, 0); + } + expect_copy_metadata(mock_metadata_copy_request, -EINVAL); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest<librbd::MockTestImageCtx>::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, CEPH_NOSNAP, 0, false, + boost::none, m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyRequest, Snap) { + EXPECT_EQ(0, snap_create(*m_src_image_ctx, "copy")); + EXPECT_EQ(0, librbd::api::Image<>::snap_set(m_src_image_ctx, + cls::rbd::UserSnapshotNamespace(), + "copy")); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockImageCopyRequest mock_image_copy_request; + MockMetadataCopyRequest mock_metadata_copy_request; + MockSnapshotCopyRequest mock_snapshot_copy_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + mock_dst_image_ctx.exclusive_lock = &mock_exclusive_lock; + + librbd::MockObjectMap *mock_object_map = + is_feature_enabled(RBD_FEATURE_OBJECT_MAP) ? + new librbd::MockObjectMap() : nullptr; + mock_dst_image_ctx.object_map = mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, 0); + expect_copy_image(mock_image_copy_request, 0); + if (mock_object_map != nullptr) { + expect_copy_object_map(mock_exclusive_lock, mock_object_map, 0); + expect_refresh_object_map(mock_dst_image_ctx, mock_object_map, 0); + } + expect_copy_metadata(mock_metadata_copy_request, 0); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs = {{m_src_image_ctx->snap_id, 123}}; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest<librbd::MockTestImageCtx>::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, m_src_image_ctx->snap_id, + 0, false, boost::none, m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyRequest, ErrorOnRollbackObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + EXPECT_EQ(0, snap_create(*m_src_image_ctx, "copy")); + EXPECT_EQ(0, librbd::api::Image<>::snap_set(m_src_image_ctx, + cls::rbd::UserSnapshotNamespace(), + "copy")); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockImageCopyRequest mock_image_copy_request; + MockMetadataCopyRequest mock_metadata_copy_request; + MockSnapshotCopyRequest mock_snapshot_copy_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + mock_dst_image_ctx.exclusive_lock = &mock_exclusive_lock; + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, 0); + expect_copy_image(mock_image_copy_request, 0); + expect_copy_object_map(mock_exclusive_lock, &mock_object_map, -EINVAL); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs = {{m_src_image_ctx->snap_id, 123}}; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest<librbd::MockTestImageCtx>::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, m_src_image_ctx->snap_id, + 0, false, boost::none, m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} diff --git a/src/test/librbd/test_mock_ExclusiveLock.cc b/src/test/librbd/test_mock_ExclusiveLock.cc new file mode 100644 index 00000000..dbdb12e7 --- /dev/null +++ b/src/test/librbd/test_mock_ExclusiveLock.cc @@ -0,0 +1,723 @@ +// -*- 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/exclusive_lock/MockPolicy.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ManagedLock.h" +#include "librbd/exclusive_lock/PreAcquireRequest.h" +#include "librbd/exclusive_lock/PostAcquireRequest.h" +#include "librbd/exclusive_lock/PreReleaseRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <list> + +namespace librbd { + +namespace { + +struct MockExclusiveLockImageCtx : public MockImageCtx { + ContextWQ *op_work_queue; + + MockExclusiveLockImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + op_work_queue = image_ctx.op_work_queue; + } +}; + +} // anonymous namespace + +namespace watcher { +template <> +struct Traits<MockExclusiveLockImageCtx> { + typedef librbd::MockImageWatcher Watcher; +}; +} + +template <> +struct ManagedLock<MockExclusiveLockImageCtx> { + ManagedLock(librados::IoCtx& ioctx, ContextWQ *work_queue, + const std::string& oid, librbd::MockImageWatcher *watcher, + managed_lock::Mode mode, bool blacklist_on_break_lock, + uint32_t blacklist_expire_seconds) + : m_lock("ManagedLock::m_lock") { + } + + virtual ~ManagedLock() = default; + + mutable Mutex m_lock; + + virtual void shutdown_handler(int r, Context *) = 0; + virtual void pre_acquire_lock_handler(Context *) = 0; + virtual void post_acquire_lock_handler(int, Context *) = 0; + virtual void pre_release_lock_handler(bool, Context *) = 0; + virtual void post_release_lock_handler(bool, int, Context *) = 0; + virtual void post_reacquire_lock_handler(int, Context *) = 0; + + MOCK_CONST_METHOD0(is_lock_owner, bool()); + + MOCK_METHOD1(shut_down, void(Context*)); + MOCK_METHOD1(acquire_lock, void(Context*)); + + void set_state_uninitialized() { + } + + MOCK_METHOD0(set_state_initializing, void()); + MOCK_METHOD0(set_state_unlocked, void()); + MOCK_METHOD0(set_state_waiting_for_lock, void()); + MOCK_METHOD0(set_state_post_acquiring, void()); + + MOCK_CONST_METHOD0(is_state_shutdown, bool()); + MOCK_CONST_METHOD0(is_state_acquiring, bool()); + MOCK_CONST_METHOD0(is_state_post_acquiring, bool()); + MOCK_CONST_METHOD0(is_state_releasing, bool()); + MOCK_CONST_METHOD0(is_state_pre_releasing, bool()); + MOCK_CONST_METHOD0(is_state_locked, bool()); + MOCK_CONST_METHOD0(is_state_waiting_for_lock, bool()); + + MOCK_CONST_METHOD0(is_action_acquire_lock, bool()); + MOCK_METHOD0(execute_next_action, void()); + +}; + +namespace exclusive_lock { + +using librbd::ImageWatcher; + +template<typename T> +struct BaseRequest { + static std::list<T *> s_requests; + Context *on_lock_unlock = nullptr; + Context *on_finish = nullptr; + + static T* create(MockExclusiveLockImageCtx &image_ctx, + Context *on_lock_unlock, Context *on_finish) { + ceph_assert(!s_requests.empty()); + T* req = s_requests.front(); + req->on_lock_unlock = on_lock_unlock; + req->on_finish = on_finish; + s_requests.pop_front(); + return req; + } + + BaseRequest() { + s_requests.push_back(reinterpret_cast<T*>(this)); + } +}; + +template<typename T> +std::list<T *> BaseRequest<T>::s_requests; + +template <> +struct PreAcquireRequest<MockExclusiveLockImageCtx> : public BaseRequest<PreAcquireRequest<MockExclusiveLockImageCtx> > { + static PreAcquireRequest<MockExclusiveLockImageCtx> *create( + MockExclusiveLockImageCtx &image_ctx, Context *on_finish) { + return BaseRequest::create(image_ctx, nullptr, on_finish); + } + MOCK_METHOD0(send, void()); +}; + +template <> +struct PostAcquireRequest<MockExclusiveLockImageCtx> : public BaseRequest<PostAcquireRequest<MockExclusiveLockImageCtx> > { + MOCK_METHOD0(send, void()); +}; + +template <> +struct PreReleaseRequest<MockExclusiveLockImageCtx> : public BaseRequest<PreReleaseRequest<MockExclusiveLockImageCtx> > { + static PreReleaseRequest<MockExclusiveLockImageCtx> *create( + MockExclusiveLockImageCtx &image_ctx, bool shutting_down, + AsyncOpTracker &async_op_tracker, Context *on_finish) { + return BaseRequest::create(image_ctx, nullptr, on_finish); + } + MOCK_METHOD0(send, void()); +}; + +} // namespace exclusive_lock +} // namespace librbd + +// template definitions +#include "librbd/ExclusiveLock.cc" + +ACTION_P(FinishLockUnlock, request) { + if (request->on_lock_unlock != nullptr) { + request->on_lock_unlock->complete(0); + } +} + +ACTION_P2(CompleteRequest, request, ret) { + request->on_finish->complete(ret); +} + +namespace librbd { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Return; + +class TestMockExclusiveLock : public TestMockFixture { +public: + typedef ManagedLock<MockExclusiveLockImageCtx> MockManagedLock; + typedef ExclusiveLock<MockExclusiveLockImageCtx> MockExclusiveLock; + typedef exclusive_lock::PreAcquireRequest<MockExclusiveLockImageCtx> MockPreAcquireRequest; + typedef exclusive_lock::PostAcquireRequest<MockExclusiveLockImageCtx> MockPostAcquireRequest; + typedef exclusive_lock::PreReleaseRequest<MockExclusiveLockImageCtx> MockPreReleaseRequest; + + void expect_set_state_initializing(MockManagedLock &managed_lock) { + EXPECT_CALL(managed_lock, set_state_initializing()); + } + + void expect_set_state_unlocked(MockManagedLock &managed_lock) { + EXPECT_CALL(managed_lock, set_state_unlocked()); + } + + void expect_set_state_waiting_for_lock(MockManagedLock &managed_lock) { + EXPECT_CALL(managed_lock, set_state_waiting_for_lock()); + } + + void expect_set_state_post_acquiring(MockManagedLock &managed_lock) { + EXPECT_CALL(managed_lock, set_state_post_acquiring()); + } + + void expect_is_state_acquiring(MockManagedLock &managed_lock, bool ret_val) { + EXPECT_CALL(managed_lock, is_state_acquiring()) + .WillOnce(Return(ret_val)); + } + + void expect_is_state_waiting_for_lock(MockManagedLock &managed_lock, + bool ret_val) { + EXPECT_CALL(managed_lock, is_state_waiting_for_lock()) + .WillOnce(Return(ret_val)); + } + + void expect_is_state_pre_releasing(MockManagedLock &managed_lock, + bool ret_val) { + EXPECT_CALL(managed_lock, is_state_pre_releasing()) + .WillOnce(Return(ret_val)); + } + + void expect_is_state_releasing(MockManagedLock &managed_lock, bool ret_val) { + EXPECT_CALL(managed_lock, is_state_releasing()) + .WillOnce(Return(ret_val)); + } + + void expect_is_state_locked(MockManagedLock &managed_lock, bool ret_val) { + EXPECT_CALL(managed_lock, is_state_locked()) + .WillOnce(Return(ret_val)); + } + + void expect_is_state_shutdown(MockManagedLock &managed_lock, bool ret_val) { + EXPECT_CALL(managed_lock, is_state_shutdown()) + .WillOnce(Return(ret_val)); + } + + void expect_is_action_acquire_lock(MockManagedLock &managed_lock, + bool ret_val) { + EXPECT_CALL(managed_lock, is_action_acquire_lock()) + .WillOnce(Return(ret_val)); + } + + void expect_set_require_lock(MockExclusiveLockImageCtx &mock_image_ctx, + io::Direction direction, bool enabled) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, set_require_lock(direction, + enabled)); + } + + void expect_block_writes(MockExclusiveLockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_work_queue, block_writes(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + if (mock_image_ctx.clone_copy_on_read || + (mock_image_ctx.features & RBD_FEATURE_JOURNALING) != 0) { + expect_set_require_lock(mock_image_ctx, io::DIRECTION_BOTH, true); + } else { + expect_set_require_lock(mock_image_ctx, io::DIRECTION_WRITE, true); + } + } + + void expect_unblock_writes(MockExclusiveLockImageCtx &mock_image_ctx) { + expect_set_require_lock(mock_image_ctx, io::DIRECTION_BOTH, false); + EXPECT_CALL(*mock_image_ctx.io_work_queue, unblock_writes()); + } + + void expect_prepare_lock_complete(MockExclusiveLockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, handle_prepare_lock_complete()); + } + + void expect_pre_acquire_request(MockPreAcquireRequest &pre_acquire_request, + int r) { + EXPECT_CALL(pre_acquire_request, send()) + .WillOnce(CompleteRequest(&pre_acquire_request, r)); + } + + void expect_post_acquire_request(MockExclusiveLock &mock_exclusive_lock, + MockPostAcquireRequest &post_acquire_request, + int r) { + EXPECT_CALL(post_acquire_request, send()) + .WillOnce(DoAll(FinishLockUnlock(&post_acquire_request), + CompleteRequest(&post_acquire_request, r))); + expect_set_state_post_acquiring(mock_exclusive_lock); + } + + void expect_pre_release_request(MockPreReleaseRequest &pre_release_request, + int r) { + EXPECT_CALL(pre_release_request, send()) + .WillOnce(CompleteRequest(&pre_release_request, r)); + } + + void expect_notify_request_lock(MockExclusiveLockImageCtx &mock_image_ctx, + MockExclusiveLock &mock_exclusive_lock) { + EXPECT_CALL(*mock_image_ctx.image_watcher, notify_request_lock()); + } + + void expect_notify_acquired_lock(MockExclusiveLockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.image_watcher, notify_acquired_lock()) + .Times(1); + } + + void expect_notify_released_lock(MockExclusiveLockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.image_watcher, notify_released_lock()) + .Times(1); + } + + void expect_flush_notifies(MockExclusiveLockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.image_watcher, flush(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_shut_down(MockManagedLock &managed_lock) { + EXPECT_CALL(managed_lock, shut_down(_)) + .WillOnce(CompleteContext(0, static_cast<ContextWQ*>(nullptr))); + } + + void expect_accept_blocked_request( + MockExclusiveLockImageCtx &mock_image_ctx, + exclusive_lock::MockPolicy &policy, + exclusive_lock::OperationRequestType request_type, bool value) { + EXPECT_CALL(mock_image_ctx, get_exclusive_lock_policy()) + .WillOnce(Return(&policy)); + EXPECT_CALL(policy, accept_blocked_request(request_type)) + .WillOnce(Return(value)); + } + + int when_init(MockExclusiveLockImageCtx &mock_image_ctx, + MockExclusiveLock &exclusive_lock) { + C_SaferCond ctx; + { + RWLock::WLocker owner_locker(mock_image_ctx.owner_lock); + exclusive_lock.init(mock_image_ctx.features, &ctx); + } + return ctx.wait(); + } + + int when_pre_acquire_lock_handler(MockManagedLock &managed_lock) { + C_SaferCond ctx; + managed_lock.pre_acquire_lock_handler(&ctx); + return ctx.wait(); + } + + int when_post_acquire_lock_handler(MockManagedLock &managed_lock, int r) { + C_SaferCond ctx; + managed_lock.post_acquire_lock_handler(r, &ctx); + return ctx.wait(); + } + + int when_pre_release_lock_handler(MockManagedLock &managed_lock, + bool shutting_down) { + C_SaferCond ctx; + managed_lock.pre_release_lock_handler(shutting_down, &ctx); + return ctx.wait(); + } + + int when_post_release_lock_handler(MockManagedLock &managed_lock, + bool shutting_down, int r) { + C_SaferCond ctx; + managed_lock.post_release_lock_handler(shutting_down, r, &ctx); + return ctx.wait(); + } + + int when_post_reacquire_lock_handler(MockManagedLock &managed_lock, int r) { + C_SaferCond ctx; + managed_lock.post_reacquire_lock_handler(r, &ctx); + return ctx.wait(); + } + + int when_shut_down(MockExclusiveLockImageCtx &mock_image_ctx, + MockExclusiveLock &exclusive_lock) { + C_SaferCond ctx; + { + RWLock::WLocker owner_locker(mock_image_ctx.owner_lock); + exclusive_lock.shut_down(&ctx); + } + return ctx.wait(); + } + + bool is_lock_owner(MockExclusiveLockImageCtx &mock_image_ctx, + MockExclusiveLock &exclusive_lock) { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + return exclusive_lock.is_lock_owner(); + } +}; + +TEST_F(TestMockExclusiveLock, StateTransitions) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockExclusiveLockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock exclusive_lock(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_set_state_initializing(exclusive_lock); + expect_block_writes(mock_image_ctx); + expect_set_state_unlocked(exclusive_lock); + ASSERT_EQ(0, when_init(mock_image_ctx, exclusive_lock)); + + // (try) acquire lock + MockPreAcquireRequest try_lock_pre_acquire; + expect_pre_acquire_request(try_lock_pre_acquire, 0); + ASSERT_EQ(0, when_pre_acquire_lock_handler(exclusive_lock)); + + MockPostAcquireRequest try_lock_post_acquire; + expect_post_acquire_request(exclusive_lock, try_lock_post_acquire, 0); + expect_is_state_acquiring(exclusive_lock, true); + expect_notify_acquired_lock(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(0, when_post_acquire_lock_handler(exclusive_lock, 0)); + + // release lock + MockPreReleaseRequest pre_request_release; + expect_pre_release_request(pre_request_release, 0); + ASSERT_EQ(0, when_pre_release_lock_handler(exclusive_lock, false)); + + expect_is_state_pre_releasing(exclusive_lock, false); + expect_is_state_releasing(exclusive_lock, true); + expect_notify_released_lock(mock_image_ctx); + ASSERT_EQ(0, when_post_release_lock_handler(exclusive_lock, false, 0)); + + // (try) acquire lock + MockPreAcquireRequest request_lock_pre_acquire; + expect_pre_acquire_request(request_lock_pre_acquire, 0); + ASSERT_EQ(0, when_pre_acquire_lock_handler(exclusive_lock)); + + MockPostAcquireRequest request_lock_post_acquire; + expect_post_acquire_request(exclusive_lock, request_lock_post_acquire, 0); + expect_is_state_acquiring(exclusive_lock, true); + expect_notify_acquired_lock(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(0, when_post_acquire_lock_handler(exclusive_lock, 0)); + + // shut down (and release) + expect_shut_down(exclusive_lock); + expect_is_state_waiting_for_lock(exclusive_lock, false); + ASSERT_EQ(0, when_shut_down(mock_image_ctx, exclusive_lock)); + + MockPreReleaseRequest shutdown_pre_release; + expect_pre_release_request(shutdown_pre_release, 0); + ASSERT_EQ(0, when_pre_release_lock_handler(exclusive_lock, true)); + + expect_unblock_writes(mock_image_ctx); + expect_notify_released_lock(mock_image_ctx); + ASSERT_EQ(0, when_post_release_lock_handler(exclusive_lock, true, 0)); +} + +TEST_F(TestMockExclusiveLock, TryLockAlreadyLocked) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockExclusiveLockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock exclusive_lock(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_set_state_initializing(exclusive_lock); + expect_block_writes(mock_image_ctx); + expect_set_state_unlocked(exclusive_lock); + ASSERT_EQ(0, when_init(mock_image_ctx, exclusive_lock)); + + // try acquire lock + MockPreAcquireRequest try_lock_pre_acquire; + expect_pre_acquire_request(try_lock_pre_acquire, 0); + ASSERT_EQ(0, when_pre_acquire_lock_handler(exclusive_lock)); + + expect_is_state_acquiring(exclusive_lock, true); + expect_prepare_lock_complete(mock_image_ctx); + expect_is_action_acquire_lock(exclusive_lock, false); + ASSERT_EQ(0, when_post_acquire_lock_handler(exclusive_lock, -EAGAIN)); +} + +TEST_F(TestMockExclusiveLock, TryLockError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockExclusiveLockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock exclusive_lock(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_set_state_initializing(exclusive_lock); + expect_block_writes(mock_image_ctx); + expect_set_state_unlocked(exclusive_lock); + ASSERT_EQ(0, when_init(mock_image_ctx, exclusive_lock)); + + // try acquire lock + MockPreAcquireRequest try_lock_pre_acquire; + expect_pre_acquire_request(try_lock_pre_acquire, 0); + ASSERT_EQ(0, when_pre_acquire_lock_handler(exclusive_lock)); + + expect_is_state_acquiring(exclusive_lock, true); + expect_prepare_lock_complete(mock_image_ctx); + expect_is_action_acquire_lock(exclusive_lock, false); + ASSERT_EQ(-EBUSY, when_post_acquire_lock_handler(exclusive_lock, -EBUSY)); +} + +TEST_F(TestMockExclusiveLock, AcquireLockAlreadyLocked) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockExclusiveLockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock exclusive_lock(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_set_state_initializing(exclusive_lock); + expect_block_writes(mock_image_ctx); + expect_set_state_unlocked(exclusive_lock); + ASSERT_EQ(0, when_init(mock_image_ctx, exclusive_lock)); + + // acquire lock + MockPreAcquireRequest try_lock_pre_acquire; + expect_pre_acquire_request(try_lock_pre_acquire, 0); + ASSERT_EQ(0, when_pre_acquire_lock_handler(exclusive_lock)); + + expect_is_state_acquiring(exclusive_lock, true); + expect_prepare_lock_complete(mock_image_ctx); + expect_is_action_acquire_lock(exclusive_lock, true); + expect_set_state_waiting_for_lock(exclusive_lock); + expect_notify_request_lock(mock_image_ctx, exclusive_lock); + ASSERT_EQ(-ECANCELED, when_post_acquire_lock_handler(exclusive_lock, + -EAGAIN)); +} + +TEST_F(TestMockExclusiveLock, AcquireLockBusy) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockExclusiveLockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock exclusive_lock(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_set_state_initializing(exclusive_lock); + expect_block_writes(mock_image_ctx); + expect_set_state_unlocked(exclusive_lock); + ASSERT_EQ(0, when_init(mock_image_ctx, exclusive_lock)); + + // acquire lock + MockPreAcquireRequest try_lock_pre_acquire; + expect_pre_acquire_request(try_lock_pre_acquire, 0); + ASSERT_EQ(0, when_pre_acquire_lock_handler(exclusive_lock)); + + expect_is_state_acquiring(exclusive_lock, true); + expect_prepare_lock_complete(mock_image_ctx); + expect_is_action_acquire_lock(exclusive_lock, true); + expect_set_state_waiting_for_lock(exclusive_lock); + expect_notify_request_lock(mock_image_ctx, exclusive_lock); + ASSERT_EQ(-ECANCELED, when_post_acquire_lock_handler(exclusive_lock, + -EBUSY)); +} + +TEST_F(TestMockExclusiveLock, AcquireLockError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockExclusiveLockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock exclusive_lock(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_set_state_initializing(exclusive_lock); + expect_block_writes(mock_image_ctx); + expect_set_state_unlocked(exclusive_lock); + ASSERT_EQ(0, when_init(mock_image_ctx, exclusive_lock)); + + // acquire lock + MockPreAcquireRequest try_lock_pre_acquire; + expect_pre_acquire_request(try_lock_pre_acquire, 0); + ASSERT_EQ(0, when_pre_acquire_lock_handler(exclusive_lock)); + + expect_is_state_acquiring(exclusive_lock, true); + expect_prepare_lock_complete(mock_image_ctx); + expect_is_action_acquire_lock(exclusive_lock, true); + ASSERT_EQ(-EBLACKLISTED, when_post_acquire_lock_handler(exclusive_lock, + -EBLACKLISTED)); +} + +TEST_F(TestMockExclusiveLock, PostAcquireLockError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockExclusiveLockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock exclusive_lock(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_set_state_initializing(exclusive_lock); + expect_block_writes(mock_image_ctx); + expect_set_state_unlocked(exclusive_lock); + ASSERT_EQ(0, when_init(mock_image_ctx, exclusive_lock)); + + // (try) acquire lock + MockPreAcquireRequest request_lock_pre_acquire; + expect_pre_acquire_request(request_lock_pre_acquire, 0); + ASSERT_EQ(0, when_pre_acquire_lock_handler(exclusive_lock)); + + MockPostAcquireRequest request_lock_post_acquire; + expect_post_acquire_request(exclusive_lock, request_lock_post_acquire, + -EPERM); + expect_is_state_acquiring(exclusive_lock, true); + ASSERT_EQ(-EPERM, when_post_acquire_lock_handler(exclusive_lock, 0)); +} + +TEST_F(TestMockExclusiveLock, PreReleaseLockError) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockExclusiveLockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock exclusive_lock(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_set_state_initializing(exclusive_lock); + expect_block_writes(mock_image_ctx); + expect_set_state_unlocked(exclusive_lock); + ASSERT_EQ(0, when_init(mock_image_ctx, exclusive_lock)); + + // release lock + MockPreReleaseRequest pre_request_release; + expect_pre_release_request(pre_request_release, -EINVAL); + ASSERT_EQ(-EINVAL, when_pre_release_lock_handler(exclusive_lock, false)); + + expect_is_state_pre_releasing(exclusive_lock, true); + ASSERT_EQ(-EINVAL, when_post_release_lock_handler(exclusive_lock, false, + -EINVAL)); +} + +TEST_F(TestMockExclusiveLock, ReacquireLock) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockExclusiveLockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock exclusive_lock(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_set_state_initializing(exclusive_lock); + expect_block_writes(mock_image_ctx); + expect_set_state_unlocked(exclusive_lock); + ASSERT_EQ(0, when_init(mock_image_ctx, exclusive_lock)); + + // (try) acquire lock + MockPreAcquireRequest try_lock_pre_acquire; + expect_pre_acquire_request(try_lock_pre_acquire, 0); + ASSERT_EQ(0, when_pre_acquire_lock_handler(exclusive_lock)); + + MockPostAcquireRequest try_lock_post_acquire; + expect_post_acquire_request(exclusive_lock, try_lock_post_acquire, 0); + expect_is_state_acquiring(exclusive_lock, true); + expect_notify_acquired_lock(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(0, when_post_acquire_lock_handler(exclusive_lock, 0)); + + // reacquire lock + expect_notify_acquired_lock(mock_image_ctx); + ASSERT_EQ(0, when_post_reacquire_lock_handler(exclusive_lock, 0)); + + // shut down (and release) + expect_shut_down(exclusive_lock); + expect_is_state_waiting_for_lock(exclusive_lock, false); + ASSERT_EQ(0, when_shut_down(mock_image_ctx, exclusive_lock)); + + MockPreReleaseRequest shutdown_pre_release; + expect_pre_release_request(shutdown_pre_release, 0); + ASSERT_EQ(0, when_pre_release_lock_handler(exclusive_lock, true)); + + expect_unblock_writes(mock_image_ctx); + expect_notify_released_lock(mock_image_ctx); + ASSERT_EQ(0, when_post_release_lock_handler(exclusive_lock, true, 0)); +} + +TEST_F(TestMockExclusiveLock, BlockRequests) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockExclusiveLockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock exclusive_lock(mock_image_ctx); + exclusive_lock::MockPolicy mock_exclusive_lock_policy; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_set_state_initializing(exclusive_lock); + expect_block_writes(mock_image_ctx); + expect_set_state_unlocked(exclusive_lock); + ASSERT_EQ(0, when_init(mock_image_ctx, exclusive_lock)); + + int ret_val; + expect_is_state_shutdown(exclusive_lock, false); + expect_is_state_locked(exclusive_lock, true); + ASSERT_TRUE(exclusive_lock.accept_request( + exclusive_lock::OPERATION_REQUEST_TYPE_GENERAL, &ret_val)); + ASSERT_EQ(0, ret_val); + + exclusive_lock.block_requests(-EROFS); + expect_is_state_shutdown(exclusive_lock, false); + expect_is_state_locked(exclusive_lock, true); + expect_accept_blocked_request(mock_image_ctx, mock_exclusive_lock_policy, + exclusive_lock::OPERATION_REQUEST_TYPE_GENERAL, + false); + ASSERT_FALSE(exclusive_lock.accept_request( + exclusive_lock::OPERATION_REQUEST_TYPE_GENERAL, &ret_val)); + ASSERT_EQ(-EROFS, ret_val); + + expect_is_state_shutdown(exclusive_lock, false); + expect_is_state_locked(exclusive_lock, true); + expect_accept_blocked_request( + mock_image_ctx, mock_exclusive_lock_policy, + exclusive_lock::OPERATION_REQUEST_TYPE_TRASH_SNAP_REMOVE, true); + ASSERT_TRUE(exclusive_lock.accept_request( + exclusive_lock::OPERATION_REQUEST_TYPE_TRASH_SNAP_REMOVE, + &ret_val)); + ASSERT_EQ(0, ret_val); + + exclusive_lock.unblock_requests(); + expect_is_state_shutdown(exclusive_lock, false); + expect_is_state_locked(exclusive_lock, true); + ASSERT_TRUE(exclusive_lock.accept_request( + exclusive_lock::OPERATION_REQUEST_TYPE_GENERAL, &ret_val)); + ASSERT_EQ(0, ret_val); +} + +} // namespace librbd + diff --git a/src/test/librbd/test_mock_Journal.cc b/src/test/librbd/test_mock_Journal.cc new file mode 100644 index 00000000..40aed36f --- /dev/null +++ b/src/test/librbd/test_mock_Journal.cc @@ -0,0 +1,1531 @@ +// -*- 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/journal/mock/MockJournaler.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockJournalPolicy.h" +#include "test/librbd/mock/io/MockObjectDispatch.h" +#include "common/Cond.h" +#include "common/Mutex.h" +#include "cls/journal/cls_journal_types.h" +#include "journal/Journaler.h" +#include "librbd/Journal.h" +#include "librbd/Utils.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/io/ObjectDispatchSpec.h" +#include "librbd/journal/Replay.h" +#include "librbd/journal/RemoveRequest.h" +#include "librbd/journal/CreateRequest.h" +#include "librbd/journal/ObjectDispatch.h" +#include "librbd/journal/OpenRequest.h" +#include "librbd/journal/Types.h" +#include "librbd/journal/TypeTraits.h" +#include "librbd/journal/PromoteRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <functional> +#include <list> +#include <boost/scope_exit.hpp> + +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_rbd + +namespace librbd { + +namespace { + +struct MockJournalImageCtx : public MockImageCtx { + MockJournalImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace journal { + +template <> +struct TypeTraits<MockJournalImageCtx> { + typedef ::journal::MockJournalerProxy Journaler; + typedef ::journal::MockFutureProxy Future; + typedef ::journal::MockReplayEntryProxy ReplayEntry; +}; + +struct MockReplay { + static MockReplay *s_instance; + static MockReplay &get_instance() { + ceph_assert(s_instance != nullptr); + return *s_instance; + } + + MockReplay() { + s_instance = this; + } + + MOCK_METHOD2(shut_down, void(bool cancel_ops, Context *)); + MOCK_METHOD2(decode, int(bufferlist::const_iterator*, EventEntry *)); + MOCK_METHOD3(process, void(const EventEntry&, Context *, Context *)); + MOCK_METHOD2(replay_op_ready, void(uint64_t, Context *)); +}; + +template <> +class Replay<MockJournalImageCtx> { +public: + static Replay *create(MockJournalImageCtx &image_ctx) { + return new Replay(); + } + + void shut_down(bool cancel_ops, Context *on_finish) { + MockReplay::get_instance().shut_down(cancel_ops, on_finish); + } + + int decode(bufferlist::const_iterator *it, EventEntry *event_entry) { + return MockReplay::get_instance().decode(it, event_entry); + } + + void process(const EventEntry& event_entry, Context *on_ready, + Context *on_commit) { + MockReplay::get_instance().process(event_entry, on_ready, on_commit); + } + + void replay_op_ready(uint64_t op_tid, Context *on_resume) { + MockReplay::get_instance().replay_op_ready(op_tid, on_resume); + } +}; + +MockReplay *MockReplay::s_instance = nullptr; + +struct MockRemove { + static MockRemove *s_instance; + static MockRemove &get_instance() { + ceph_assert(s_instance != nullptr); + return *s_instance; + } + + MockRemove() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template <> +class RemoveRequest<MockJournalImageCtx> { +public: + static RemoveRequest *create(IoCtx &ioctx, const std::string &imageid, + const std::string &client_id, + ContextWQ *op_work_queue, Context *on_finish) { + return new RemoveRequest(); + } + + void send() { + MockRemove::get_instance().send(); + } +}; + +MockRemove *MockRemove::s_instance = nullptr; + +struct MockCreate { + static MockCreate *s_instance; + static MockCreate &get_instance() { + ceph_assert(s_instance != nullptr); + return *s_instance; + } + + MockCreate() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template<> +class CreateRequest<MockJournalImageCtx> { +public: + static CreateRequest *create(IoCtx &ioctx, const std::string &imageid, + uint8_t order, uint8_t splay_width, + const std::string &object_pool, + uint64_t tag_class, TagData &tag_data, + const std::string &client_id, + ContextWQ *op_work_queue, Context *on_finish) { + return new CreateRequest(); + } + + void send() { + MockCreate::get_instance().send(); + } +}; + +MockCreate *MockCreate::s_instance = nullptr; + +template<> +class OpenRequest<MockJournalImageCtx> { +public: + TagData *tag_data; + Context *on_finish; + static OpenRequest *s_instance; + static OpenRequest *create(MockJournalImageCtx *image_ctx, + ::journal::MockJournalerProxy *journaler, + Mutex *lock, journal::ImageClientMeta *client_meta, + uint64_t *tag_tid, journal::TagData *tag_data, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->tag_data = tag_data; + s_instance->on_finish = on_finish; + return s_instance; + } + + OpenRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +OpenRequest<MockJournalImageCtx> *OpenRequest<MockJournalImageCtx>::s_instance = nullptr; + + +template <> +class PromoteRequest<MockJournalImageCtx> { +public: + static PromoteRequest s_instance; + static PromoteRequest *create(MockJournalImageCtx *image_ctx, bool force, + Context *on_finish) { + return &s_instance; + } + + MOCK_METHOD0(send, void()); +}; + +PromoteRequest<MockJournalImageCtx> PromoteRequest<MockJournalImageCtx>::s_instance; + +template <> +struct ObjectDispatch<MockJournalImageCtx> : public io::MockObjectDispatch { + static ObjectDispatch* s_instance; + + static ObjectDispatch* create(MockJournalImageCtx* image_ctx, + Journal<MockJournalImageCtx>* journal) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + ObjectDispatch() { + s_instance = this; + } +}; + +ObjectDispatch<MockJournalImageCtx>* ObjectDispatch<MockJournalImageCtx>::s_instance = nullptr; + +} // namespace journal +} // namespace librbd + +// template definitions +#include "librbd/Journal.cc" + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::MatcherCast; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::SetArgPointee; +using ::testing::WithArg; +using namespace std::placeholders; + +ACTION_P2(StartReplay, wq, ctx) { + wq->queue(ctx, 0); +} + +namespace librbd { + +class TestMockJournal : public TestMockFixture { +public: + typedef journal::MockReplay MockJournalReplay; + typedef Journal<MockJournalImageCtx> MockJournal; + typedef journal::OpenRequest<MockJournalImageCtx> MockJournalOpenRequest; + typedef journal::ObjectDispatch<MockJournalImageCtx> MockObjectDispatch; + typedef std::function<void(::journal::ReplayHandler*)> ReplayAction; + typedef std::list<Context *> Contexts; + + TestMockJournal() : m_lock("lock") { + } + + ~TestMockJournal() override { + ceph_assert(m_commit_contexts.empty()); + } + + Mutex m_lock; + Cond m_cond; + Contexts m_commit_contexts; + + struct C_ReplayAction : public Context { + ::journal::ReplayHandler **replay_handler; + ReplayAction replay_action; + + C_ReplayAction(::journal::ReplayHandler **replay_handler, + const ReplayAction &replay_action) + : replay_handler(replay_handler), replay_action(replay_action) { + } + void finish(int r) override { + if (replay_action) { + replay_action(*replay_handler); + } + } + }; + + void expect_construct_journaler(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, construct()); + } + + void expect_open_journaler(MockImageCtx &mock_image_ctx, + ::journal::MockJournaler &mock_journaler, + MockJournalOpenRequest &mock_open_request, + bool primary, int r) { + EXPECT_CALL(mock_journaler, add_listener(_)) + .WillOnce(SaveArg<0>(&m_listener)); + EXPECT_CALL(mock_open_request, send()) + .WillOnce(DoAll(Invoke([&mock_open_request, primary]() { + if (!primary) { + mock_open_request.tag_data->mirror_uuid = "remote mirror uuid"; + } + }), + FinishRequest(&mock_open_request, r, + &mock_image_ctx))); + } + + void expect_shut_down_journaler(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, remove_listener(_)); + EXPECT_CALL(mock_journaler, shut_down(_)) + .WillOnce(CompleteContext(0, static_cast<ContextWQ*>(NULL))); + } + + void expect_register_object_dispatch(MockImageCtx& mock_image_ctx, + MockObjectDispatch& mock_object_dispatch) { + EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, + register_object_dispatch(&mock_object_dispatch)); + } + + void expect_shut_down_object_dispatch(MockImageCtx& mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, + shut_down_object_dispatch(io::OBJECT_DISPATCH_LAYER_JOURNAL, _)) + .WillOnce(WithArg<1>(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue))); + } + + void expect_get_max_append_size(::journal::MockJournaler &mock_journaler, + uint32_t max_size) { + EXPECT_CALL(mock_journaler, get_max_append_size()) + .WillOnce(Return(max_size)); + } + + void expect_get_journaler_cached_client(::journal::MockJournaler &mock_journaler, + const journal::ImageClientMeta &client_meta, + int r) { + journal::ClientData client_data; + client_data.client_meta = client_meta; + + cls::journal::Client client; + encode(client_data, client.data); + + EXPECT_CALL(mock_journaler, get_cached_client("", _)) + .WillOnce(DoAll(SetArgPointee<1>(client), + Return(r))); + } + + void expect_get_journaler_tags(MockImageCtx &mock_image_ctx, + ::journal::MockJournaler &mock_journaler, + uint64_t start_after_tag_tid, + ::journal::Journaler::Tags &&tags, int r) { + EXPECT_CALL(mock_journaler, get_tags(start_after_tag_tid, 0, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(tags), + WithArg<3>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)))); + } + + void expect_start_replay(MockJournalImageCtx &mock_image_ctx, + ::journal::MockJournaler &mock_journaler, + const ReplayAction &action) { + EXPECT_CALL(mock_journaler, start_replay(_)) + .WillOnce(DoAll(SaveArg<0>(&m_replay_handler), + StartReplay(mock_image_ctx.image_ctx->op_work_queue, + new C_ReplayAction(&m_replay_handler, + action)))); + } + + void expect_stop_replay(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, stop_replay(_)) + .WillOnce(CompleteContext(0, static_cast<ContextWQ*>(NULL))); + } + + void expect_shut_down_replay(MockJournalImageCtx &mock_image_ctx, + MockJournalReplay &mock_journal_replay, int r, + bool cancel_ops = false) { + EXPECT_CALL(mock_journal_replay, shut_down(cancel_ops, _)) + .WillOnce(WithArg<1>(Invoke([this, &mock_image_ctx, r](Context *on_flush) { + this->commit_replay(mock_image_ctx, on_flush, r);}))); + } + + void expect_get_data(::journal::MockReplayEntry &mock_replay_entry) { + EXPECT_CALL(mock_replay_entry, get_data()) + .WillOnce(Return(bufferlist())); + } + + void expect_try_pop_front(MockJournalImageCtx &mock_image_ctx, + ::journal::MockJournaler &mock_journaler, + bool entries_available, + ::journal::MockReplayEntry &mock_replay_entry, + const ReplayAction &action = {}) { + EXPECT_CALL(mock_journaler, try_pop_front(_)) + .WillOnce(DoAll(SetArgPointee<0>(::journal::MockReplayEntryProxy()), + StartReplay(mock_image_ctx.image_ctx->op_work_queue, + new C_ReplayAction(&m_replay_handler, + action)), + Return(entries_available))); + if (entries_available) { + expect_get_data(mock_replay_entry); + } + } + + void expect_replay_process(MockJournalReplay &mock_journal_replay) { + EXPECT_CALL(mock_journal_replay, decode(_, _)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_journal_replay, process(_, _, _)) + .WillOnce(DoAll(WithArg<1>(CompleteContext(0, static_cast<ContextWQ*>(NULL))), + WithArg<2>(Invoke(this, &TestMockJournal::save_commit_context)))); + } + + void expect_start_append(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, start_append(_)); + } + + void expect_set_append_batch_options(MockJournalImageCtx &mock_image_ctx, + ::journal::MockJournaler &mock_journaler, + bool user_flushed) { + if (mock_image_ctx.image_ctx->config.get_val<bool>("rbd_journal_object_writethrough_until_flush") == + user_flushed) { + EXPECT_CALL(mock_journaler, set_append_batch_options(_, _, _)); + } + } + + void expect_stop_append(::journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, stop_append(_)) + .WillOnce(CompleteContext(r, static_cast<ContextWQ*>(NULL))); + } + + void expect_committed(::journal::MockJournaler &mock_journaler, + size_t events) { + EXPECT_CALL(mock_journaler, committed(MatcherCast<const ::journal::MockReplayEntryProxy&>(_))) + .Times(events); + } + + void expect_append_journaler(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, append(_, _)) + .WillOnce(Return(::journal::MockFutureProxy())); + } + + void expect_wait_future(::journal::MockFuture &mock_future, + Context **on_safe) { + EXPECT_CALL(mock_future, wait(_)) + .WillOnce(SaveArg<0>(on_safe)); + } + + void expect_future_committed(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, committed(MatcherCast<const ::journal::MockFutureProxy&>(_))); + } + + void expect_future_is_valid(::journal::MockFuture &mock_future) { + EXPECT_CALL(mock_future, is_valid()).WillOnce(Return(false)); + } + + void expect_flush_commit_position(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, flush_commit_position(_)) + .WillOnce(CompleteContext(0, static_cast<ContextWQ*>(NULL))); + } + + int when_open(MockJournal &mock_journal) { + C_SaferCond ctx; + mock_journal.open(&ctx); + return ctx.wait(); + } + + int when_close(MockJournal &mock_journal) { + C_SaferCond ctx; + mock_journal.close(&ctx); + return ctx.wait(); + } + + uint64_t when_append_write_event(MockJournalImageCtx &mock_image_ctx, + MockJournal &mock_journal, uint64_t length) { + bufferlist bl; + bl.append_zero(length); + + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + return mock_journal.append_write_event(0, length, bl, false); + } + + uint64_t when_append_io_event(MockJournalImageCtx &mock_image_ctx, + MockJournal &mock_journal, + int filter_ret_val) { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + return mock_journal.append_io_event( + journal::EventEntry{journal::AioFlushEvent{}}, 0, 0, false, + filter_ret_val); + } + + void save_commit_context(Context *ctx) { + Mutex::Locker locker(m_lock); + m_commit_contexts.push_back(ctx); + m_cond.Signal(); + } + + void wake_up() { + Mutex::Locker locker(m_lock); + m_cond.Signal(); + } + + void commit_replay(MockJournalImageCtx &mock_image_ctx, Context *on_flush, + int r) { + Contexts commit_contexts; + std::swap(commit_contexts, m_commit_contexts); + + derr << "SHUT DOWN REPLAY START" << dendl; + for (auto ctx : commit_contexts) { + mock_image_ctx.image_ctx->op_work_queue->queue(ctx, r); + } + + on_flush = new FunctionContext([on_flush](int r) { + derr << "FLUSH START" << dendl; + on_flush->complete(r); + derr << "FLUSH FINISH" << dendl; + }); + mock_image_ctx.image_ctx->op_work_queue->queue(on_flush, 0); + derr << "SHUT DOWN REPLAY FINISH" << dendl; + } + + void open_journal(MockJournalImageCtx &mock_image_ctx, + MockJournal &mock_journal, + MockObjectDispatch& mock_object_dispatch, + ::journal::MockJournaler &mock_journaler, + MockJournalOpenRequest &mock_open_request, + bool primary = true) { + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_register_object_dispatch(mock_image_ctx, mock_object_dispatch); + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + primary, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_complete, _1, 0)); + + MockJournalReplay mock_journal_replay; + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0); + expect_committed(mock_journaler, 0); + expect_flush_commit_position(mock_journaler); + expect_start_append(mock_journaler); + expect_set_append_batch_options(mock_image_ctx, mock_journaler, false); + ASSERT_EQ(0, when_open(mock_journal)); + } + + void close_journal(MockJournalImageCtx& mock_image_ctx, + MockJournal &mock_journal, + ::journal::MockJournaler &mock_journaler) { + expect_stop_append(mock_journaler, 0); + expect_shut_down_object_dispatch(mock_image_ctx); + ASSERT_EQ(0, when_close(mock_journal)); + } + + static void invoke_replay_ready(::journal::ReplayHandler *handler) { + handler->handle_entries_available(); + } + + static void invoke_replay_complete(::journal::ReplayHandler *handler, int r) { + handler->handle_complete(r); + } + + ::journal::ReplayHandler *m_replay_handler = nullptr; + ::journal::JournalMetadataListener *m_listener = nullptr; +}; + +TEST_F(TestMockJournal, StateTransitions) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + MockObjectDispatch mock_object_dispatch; + expect_register_object_dispatch(mock_image_ctx, mock_object_dispatch); + + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_ready, _1)); + + ::journal::MockReplayEntry mock_replay_entry; + MockJournalReplay mock_journal_replay; + expect_try_pop_front(mock_image_ctx, mock_journaler, true, mock_replay_entry); + expect_replay_process(mock_journal_replay); + expect_try_pop_front(mock_image_ctx, mock_journaler, true, mock_replay_entry); + expect_replay_process(mock_journal_replay); + expect_try_pop_front(mock_image_ctx, mock_journaler, false, mock_replay_entry, + std::bind(&invoke_replay_ready, _1)); + expect_try_pop_front(mock_image_ctx, mock_journaler, true, mock_replay_entry); + expect_replay_process(mock_journal_replay); + expect_try_pop_front(mock_image_ctx, mock_journaler, false, mock_replay_entry, + std::bind(&invoke_replay_complete, _1, 0)); + + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0); + expect_committed(mock_journaler, 3); + expect_flush_commit_position(mock_journaler); + + expect_start_append(mock_journaler); + expect_set_append_batch_options(mock_image_ctx, mock_journaler, false); + + ASSERT_EQ(0, when_open(mock_journal)); + + expect_stop_append(mock_journaler, 0); + expect_shut_down_journaler(mock_journaler); + expect_shut_down_object_dispatch(mock_image_ctx); + ASSERT_EQ(0, when_close(mock_journal)); +} + +TEST_F(TestMockJournal, InitError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + MockObjectDispatch mock_object_dispatch; + expect_register_object_dispatch(mock_image_ctx, mock_object_dispatch); + + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, -EINVAL); + expect_shut_down_journaler(mock_journaler); + ASSERT_EQ(-EINVAL, when_open(mock_journal)); +} + +TEST_F(TestMockJournal, ReplayCompleteError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + MockObjectDispatch mock_object_dispatch; + expect_register_object_dispatch(mock_image_ctx, mock_object_dispatch); + + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_complete, _1, -EINVAL)); + + MockJournalReplay mock_journal_replay; + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0, true); + expect_flush_commit_position(mock_journaler); + expect_shut_down_journaler(mock_journaler); + + // replay failure should result in replay-restart + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_complete, _1, 0)); + + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0); + expect_flush_commit_position(mock_journaler); + expect_start_append(mock_journaler); + expect_set_append_batch_options(mock_image_ctx, mock_journaler, false); + ASSERT_EQ(0, when_open(mock_journal)); + + expect_stop_append(mock_journaler, 0); + expect_shut_down_journaler(mock_journaler); + expect_shut_down_object_dispatch(mock_image_ctx); + ASSERT_EQ(0, when_close(mock_journal)); +} + +TEST_F(TestMockJournal, FlushReplayError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + MockObjectDispatch mock_object_dispatch; + expect_register_object_dispatch(mock_image_ctx, mock_object_dispatch); + + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_ready, _1)); + + ::journal::MockReplayEntry mock_replay_entry; + MockJournalReplay mock_journal_replay; + expect_try_pop_front(mock_image_ctx, mock_journaler, true, mock_replay_entry); + expect_replay_process(mock_journal_replay); + expect_try_pop_front(mock_image_ctx, mock_journaler, false, mock_replay_entry, + std::bind(&invoke_replay_complete, _1, 0)); + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, -EINVAL); + expect_flush_commit_position(mock_journaler); + expect_shut_down_journaler(mock_journaler); + + // replay flush failure should result in replay-restart + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_complete, _1, 0)); + + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0); + expect_flush_commit_position(mock_journaler); + expect_start_append(mock_journaler); + expect_set_append_batch_options(mock_image_ctx, mock_journaler, false); + ASSERT_EQ(0, when_open(mock_journal)); + + expect_stop_append(mock_journaler, 0); + expect_shut_down_journaler(mock_journaler); + expect_shut_down_object_dispatch(mock_image_ctx); + ASSERT_EQ(0, when_close(mock_journal)); +} + +TEST_F(TestMockJournal, CorruptEntry) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + MockObjectDispatch mock_object_dispatch; + expect_register_object_dispatch(mock_image_ctx, mock_object_dispatch); + + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_ready, _1)); + + ::journal::MockReplayEntry mock_replay_entry; + MockJournalReplay mock_journal_replay; + expect_try_pop_front(mock_image_ctx, mock_journaler, true, mock_replay_entry); + EXPECT_CALL(mock_journal_replay, decode(_, _)).WillOnce(Return(-EBADMSG)); + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0, true); + expect_flush_commit_position(mock_journaler); + expect_shut_down_journaler(mock_journaler); + + // replay failure should result in replay-restart + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_complete, _1, 0)); + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0); + expect_flush_commit_position(mock_journaler); + expect_start_append(mock_journaler); + expect_set_append_batch_options(mock_image_ctx, mock_journaler, false); + ASSERT_EQ(0, when_open(mock_journal)); + + expect_stop_append(mock_journaler, -EINVAL); + expect_shut_down_journaler(mock_journaler); + expect_shut_down_object_dispatch(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_close(mock_journal)); +} + +TEST_F(TestMockJournal, StopError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + MockObjectDispatch mock_object_dispatch; + expect_register_object_dispatch(mock_image_ctx, mock_object_dispatch); + + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_complete, _1, 0)); + + MockJournalReplay mock_journal_replay; + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0); + expect_flush_commit_position(mock_journaler); + expect_start_append(mock_journaler); + expect_set_append_batch_options(mock_image_ctx, mock_journaler, false); + ASSERT_EQ(0, when_open(mock_journal)); + + expect_stop_append(mock_journaler, -EINVAL); + expect_shut_down_journaler(mock_journaler); + expect_shut_down_object_dispatch(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_close(mock_journal)); +} + +TEST_F(TestMockJournal, ReplayOnDiskPreFlushError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + MockObjectDispatch mock_object_dispatch; + expect_register_object_dispatch(mock_image_ctx, mock_object_dispatch); + + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_ready, _1)); + + ::journal::MockReplayEntry mock_replay_entry; + MockJournalReplay mock_journal_replay; + expect_try_pop_front(mock_image_ctx, mock_journaler, true, mock_replay_entry); + + EXPECT_CALL(mock_journal_replay, decode(_, _)) + .WillOnce(Return(0)); + Context *on_ready; + EXPECT_CALL(mock_journal_replay, process(_, _, _)) + .WillOnce(DoAll(SaveArg<1>(&on_ready), + WithArg<2>(Invoke(this, &TestMockJournal::save_commit_context)))); + + expect_try_pop_front(mock_image_ctx, mock_journaler, false, + mock_replay_entry); + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0, true); + expect_flush_commit_position(mock_journaler); + expect_shut_down_journaler(mock_journaler); + + // replay write-to-disk failure should result in replay-restart + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, { + std::bind(&invoke_replay_complete, _1, 0) + }); + + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0); + expect_flush_commit_position(mock_journaler); + expect_start_append(mock_journaler); + expect_set_append_batch_options(mock_image_ctx, mock_journaler, false); + + C_SaferCond ctx; + mock_journal.open(&ctx); + + // wait for the process callback + { + Mutex::Locker locker(m_lock); + while (m_commit_contexts.empty()) { + m_cond.Wait(m_lock); + } + } + on_ready->complete(0); + + // inject RADOS error in the middle of replay + Context *on_safe = m_commit_contexts.front(); + m_commit_contexts.clear(); + on_safe->complete(-EINVAL); + + // flag the replay as complete + m_replay_handler->handle_complete(0); + + ASSERT_EQ(0, ctx.wait()); + + expect_stop_append(mock_journaler, 0); + expect_shut_down_journaler(mock_journaler); + expect_shut_down_object_dispatch(mock_image_ctx); + ASSERT_EQ(0, when_close(mock_journal)); +} + +TEST_F(TestMockJournal, ReplayOnDiskPostFlushError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + MockObjectDispatch mock_object_dispatch; + expect_register_object_dispatch(mock_image_ctx, mock_object_dispatch); + + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_ready, _1)); + + ::journal::MockReplayEntry mock_replay_entry; + MockJournalReplay mock_journal_replay; + expect_try_pop_front(mock_image_ctx, mock_journaler, true, mock_replay_entry); + expect_replay_process(mock_journal_replay); + expect_try_pop_front(mock_image_ctx, mock_journaler, false, mock_replay_entry, + std::bind(&invoke_replay_complete, _1, 0)); + expect_stop_replay(mock_journaler); + + Context *on_flush = nullptr; + EXPECT_CALL(mock_journal_replay, shut_down(false, _)) + .WillOnce(DoAll(SaveArg<1>(&on_flush), + InvokeWithoutArgs(this, &TestMockJournal::wake_up))); + expect_flush_commit_position(mock_journaler); + + // replay write-to-disk failure should result in replay-restart + expect_shut_down_journaler(mock_journaler); + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_journaler, mock_open_request, + true, 0); + expect_get_max_append_size(mock_journaler, 1 << 16); + expect_start_replay( + mock_image_ctx, mock_journaler, + std::bind(&invoke_replay_complete, _1, 0)); + + expect_stop_replay(mock_journaler); + expect_shut_down_replay(mock_image_ctx, mock_journal_replay, 0); + expect_flush_commit_position(mock_journaler); + expect_start_append(mock_journaler); + expect_set_append_batch_options(mock_image_ctx, mock_journaler, false); + + C_SaferCond ctx; + mock_journal.open(&ctx); + + // proceed with the flush + { + // wait for on_flush callback + Mutex::Locker locker(m_lock); + while (on_flush == nullptr) { + m_cond.Wait(m_lock); + } + } + + { + // wait for the on_safe process callback + Mutex::Locker locker(m_lock); + while (m_commit_contexts.empty()) { + m_cond.Wait(m_lock); + } + } + m_commit_contexts.front()->complete(-EINVAL); + m_commit_contexts.clear(); + on_flush->complete(0); + + ASSERT_EQ(0, ctx.wait()); + + expect_stop_append(mock_journaler, 0); + expect_shut_down_journaler(mock_journaler); + expect_shut_down_object_dispatch(mock_image_ctx); + ASSERT_EQ(0, when_close(mock_journal)); +} + +TEST_F(TestMockJournal, EventAndIOCommitOrder) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + BOOST_SCOPE_EXIT_ALL(&) { + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + ::journal::MockFuture mock_future; + Context *on_journal_safe1; + expect_append_journaler(mock_journaler); + expect_wait_future(mock_future, &on_journal_safe1); + ASSERT_EQ(1U, when_append_io_event(mock_image_ctx, mock_journal, 0)); + mock_journal.get_work_queue()->drain(); + + Context *on_journal_safe2; + expect_append_journaler(mock_journaler); + expect_wait_future(mock_future, &on_journal_safe2); + ASSERT_EQ(2U, when_append_io_event(mock_image_ctx, mock_journal, 0)); + mock_journal.get_work_queue()->drain(); + + // commit journal event followed by IO event (standard) + on_journal_safe1->complete(0); + ictx->op_work_queue->drain(); + expect_future_committed(mock_journaler); + mock_journal.commit_io_event(1U, 0); + + // commit IO event followed by journal event (cache overwrite) + mock_journal.commit_io_event(2U, 0); + expect_future_committed(mock_journaler); + + C_SaferCond event_ctx; + mock_journal.wait_event(2U, &event_ctx); + on_journal_safe2->complete(0); + ictx->op_work_queue->drain(); + ASSERT_EQ(0, event_ctx.wait()); + + expect_shut_down_journaler(mock_journaler); +} + +TEST_F(TestMockJournal, AppendWriteEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + BOOST_SCOPE_EXIT_ALL(&) { + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + InSequence seq; + + ::journal::MockFuture mock_future; + Context *on_journal_safe = nullptr; + expect_append_journaler(mock_journaler); + expect_append_journaler(mock_journaler); + expect_append_journaler(mock_journaler); + expect_wait_future(mock_future, &on_journal_safe); + ASSERT_EQ(1U, when_append_write_event(mock_image_ctx, mock_journal, 1 << 17)); + mock_journal.get_work_queue()->drain(); + + on_journal_safe->complete(0); + C_SaferCond event_ctx; + mock_journal.wait_event(1U, &event_ctx); + ASSERT_EQ(0, event_ctx.wait()); + + expect_future_committed(mock_journaler); + expect_future_committed(mock_journaler); + expect_future_committed(mock_journaler); + mock_journal.commit_io_event(1U, 0); + ictx->op_work_queue->drain(); + + expect_shut_down_journaler(mock_journaler); +} + +TEST_F(TestMockJournal, EventCommitError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + BOOST_SCOPE_EXIT_ALL(&) { + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + ::journal::MockFuture mock_future; + Context *on_journal_safe; + expect_append_journaler(mock_journaler); + expect_wait_future(mock_future, &on_journal_safe); + ASSERT_EQ(1U, when_append_io_event(mock_image_ctx, mock_journal, 0)); + mock_journal.get_work_queue()->drain(); + + // commit the event in the journal w/o waiting writeback + expect_future_committed(mock_journaler); + C_SaferCond object_request_ctx; + mock_journal.wait_event(1U, &object_request_ctx); + on_journal_safe->complete(-EINVAL); + ASSERT_EQ(-EINVAL, object_request_ctx.wait()); + + // cache should receive the error after attempting writeback + expect_future_is_valid(mock_future); + C_SaferCond flush_ctx; + mock_journal.flush_event(1U, &flush_ctx); + ASSERT_EQ(-EINVAL, flush_ctx.wait()); + + expect_shut_down_journaler(mock_journaler); +} + +TEST_F(TestMockJournal, EventCommitErrorWithPendingWriteback) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + BOOST_SCOPE_EXIT_ALL(&) { + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + ::journal::MockFuture mock_future; + Context *on_journal_safe; + expect_append_journaler(mock_journaler); + expect_wait_future(mock_future, &on_journal_safe); + ASSERT_EQ(1U, when_append_io_event(mock_image_ctx, mock_journal, 0)); + mock_journal.get_work_queue()->drain(); + + expect_future_is_valid(mock_future); + C_SaferCond flush_ctx; + mock_journal.flush_event(1U, &flush_ctx); + + // commit the event in the journal w/ waiting cache writeback + expect_future_committed(mock_journaler); + C_SaferCond object_request_ctx; + mock_journal.wait_event(1U, &object_request_ctx); + on_journal_safe->complete(-EINVAL); + ASSERT_EQ(-EINVAL, object_request_ctx.wait()); + + // cache should receive the error if waiting + ASSERT_EQ(-EINVAL, flush_ctx.wait()); + + expect_shut_down_journaler(mock_journaler); +} + +TEST_F(TestMockJournal, IOCommitError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + BOOST_SCOPE_EXIT_ALL(&) { + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + ::journal::MockFuture mock_future; + Context *on_journal_safe; + expect_append_journaler(mock_journaler); + expect_wait_future(mock_future, &on_journal_safe); + ASSERT_EQ(1U, when_append_io_event(mock_image_ctx, mock_journal, 0)); + mock_journal.get_work_queue()->drain(); + + // failed IO remains uncommitted in journal + on_journal_safe->complete(0); + ictx->op_work_queue->drain(); + mock_journal.commit_io_event(1U, -EINVAL); + + expect_shut_down_journaler(mock_journaler); +} + +TEST_F(TestMockJournal, IOCommitErrorFiltered) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + BOOST_SCOPE_EXIT_ALL(&) { + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + ::journal::MockFuture mock_future; + Context *on_journal_safe; + expect_append_journaler(mock_journaler); + expect_wait_future(mock_future, &on_journal_safe); + ASSERT_EQ(1U, when_append_io_event(mock_image_ctx, mock_journal, -EILSEQ)); + mock_journal.get_work_queue()->drain(); + + // filter failed IO committed in journal + on_journal_safe->complete(0); + ictx->op_work_queue->drain(); + expect_future_committed(mock_journaler); + mock_journal.commit_io_event(1U, -EILSEQ); + + expect_shut_down_journaler(mock_journaler); +} + +TEST_F(TestMockJournal, FlushCommitPosition) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + BOOST_SCOPE_EXIT_ALL(&) { + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + expect_flush_commit_position(mock_journaler); + C_SaferCond ctx; + mock_journal.flush_commit_position(&ctx); + ASSERT_EQ(0, ctx.wait()); + + expect_shut_down_journaler(mock_journaler); +} + +TEST_F(TestMockJournal, ExternalReplay) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + BOOST_SCOPE_EXIT_ALL(&) { + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + InSequence seq; + expect_stop_append(mock_journaler, 0); + expect_start_append(mock_journaler); + expect_set_append_batch_options(mock_image_ctx, mock_journaler, false); + expect_shut_down_journaler(mock_journaler); + + C_SaferCond start_ctx; + + journal::Replay<MockJournalImageCtx> *journal_replay = nullptr; + mock_journal.start_external_replay(&journal_replay, &start_ctx); + ASSERT_EQ(0, start_ctx.wait()); + + mock_journal.stop_external_replay(); +} + +TEST_F(TestMockJournal, ExternalReplayFailure) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + BOOST_SCOPE_EXIT_ALL(&) { + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + InSequence seq; + expect_stop_append(mock_journaler, -EINVAL); + expect_start_append(mock_journaler); + expect_set_append_batch_options(mock_image_ctx, mock_journaler, false); + expect_shut_down_journaler(mock_journaler); + + C_SaferCond start_ctx; + + journal::Replay<MockJournalImageCtx> *journal_replay = nullptr; + mock_journal.start_external_replay(&journal_replay, &start_ctx); + ASSERT_EQ(-EINVAL, start_ctx.wait()); +} + +TEST_F(TestMockJournal, AppendDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + MockJournalPolicy mock_journal_policy; + + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + BOOST_SCOPE_EXIT_ALL(&) { + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + InSequence seq; + RWLock::RLocker snap_locker(mock_image_ctx.snap_lock); + EXPECT_CALL(mock_image_ctx, get_journal_policy()).WillOnce( + Return(ictx->get_journal_policy())); + ASSERT_TRUE(mock_journal.is_journal_appending()); + + EXPECT_CALL(mock_image_ctx, get_journal_policy()).WillOnce( + Return(&mock_journal_policy)); + EXPECT_CALL(mock_journal_policy, append_disabled()).WillOnce(Return(true)); + ASSERT_FALSE(mock_journal.is_journal_appending()); + + expect_shut_down_journaler(mock_journaler); +} + +TEST_F(TestMockJournal, CloseListenerEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + + struct Listener : public journal::Listener { + C_SaferCond ctx; + void handle_close() override { + ctx.complete(0); + } + void handle_resync() override { + ADD_FAILURE() << "unexpected resync request"; + } + void handle_promoted() override { + ADD_FAILURE() << "unexpected promotion event"; + } + } listener; + mock_journal.add_listener(&listener); + + expect_shut_down_journaler(mock_journaler); + close_journal(mock_image_ctx, mock_journal, mock_journaler); + + ASSERT_EQ(0, listener.ctx.wait()); + mock_journal.remove_listener(&listener); +} + +TEST_F(TestMockJournal, ResyncRequested) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request, + false); + + struct Listener : public journal::Listener { + C_SaferCond ctx; + void handle_close() override { + ADD_FAILURE() << "unexpected close action"; + } + void handle_resync() override { + ctx.complete(0); + } + void handle_promoted() override { + ADD_FAILURE() << "unexpected promotion event"; + } + } listener; + mock_journal.add_listener(&listener); + + BOOST_SCOPE_EXIT_ALL(&) { + mock_journal.remove_listener(&listener); + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + InSequence seq; + + journal::TagData tag_data; + tag_data.mirror_uuid = Journal<>::LOCAL_MIRROR_UUID; + + bufferlist tag_data_bl; + encode(tag_data, tag_data_bl); + expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0, + {{0, 0, tag_data_bl}}, 0); + + journal::ImageClientMeta image_client_meta; + image_client_meta.tag_class = 0; + image_client_meta.resync_requested = true; + expect_get_journaler_cached_client(mock_journaler, image_client_meta, 0); + expect_shut_down_journaler(mock_journaler); + + m_listener->handle_update(nullptr); + ASSERT_EQ(0, listener.ctx.wait()); +} + +TEST_F(TestMockJournal, ForcePromoted) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request, false); + + struct Listener : public journal::Listener { + C_SaferCond ctx; + void handle_close() override { + ADD_FAILURE() << "unexpected close action"; + } + void handle_resync() override { + ADD_FAILURE() << "unexpected resync event"; + } + void handle_promoted() override { + ctx.complete(0); + } + } listener; + mock_journal.add_listener(&listener); + + BOOST_SCOPE_EXIT_ALL(&) { + mock_journal.remove_listener(&listener); + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + InSequence seq; + + journal::TagData tag_data; + tag_data.mirror_uuid = Journal<>::LOCAL_MIRROR_UUID; + + bufferlist tag_data_bl; + encode(tag_data, tag_data_bl); + expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0, + {{100, 0, tag_data_bl}}, 0); + + journal::ImageClientMeta image_client_meta; + image_client_meta.tag_class = 0; + expect_get_journaler_cached_client(mock_journaler, image_client_meta, 0); + expect_shut_down_journaler(mock_journaler); + + m_listener->handle_update(nullptr); + ASSERT_EQ(0, listener.ctx.wait()); +} + +TEST_F(TestMockJournal, UserFlushed) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockJournalImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal(mock_image_ctx); + MockObjectDispatch mock_object_dispatch; + ::journal::MockJournaler mock_journaler; + MockJournalOpenRequest mock_open_request; + open_journal(mock_image_ctx, mock_journal, mock_object_dispatch, + mock_journaler, mock_open_request); + BOOST_SCOPE_EXIT_ALL(&) { + close_journal(mock_image_ctx, mock_journal, mock_journaler); + }; + + expect_set_append_batch_options(mock_image_ctx, mock_journaler, true); + mock_journal.user_flushed(); + + expect_shut_down_journaler(mock_journaler); +} + +} // namespace librbd diff --git a/src/test/librbd/test_mock_ManagedLock.cc b/src/test/librbd/test_mock_ManagedLock.cc new file mode 100644 index 00000000..921d9f50 --- /dev/null +++ b/src/test/librbd/test_mock_ManagedLock.cc @@ -0,0 +1,691 @@ +// -*- 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 "librbd/ManagedLock.h" +#include "librbd/managed_lock/AcquireRequest.h" +#include "librbd/managed_lock/BreakRequest.h" +#include "librbd/managed_lock/GetLockerRequest.h" +#include "librbd/managed_lock/ReacquireRequest.h" +#include "librbd/managed_lock/ReleaseRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <list> + +namespace librbd { + +struct MockManagedLockImageCtx : public MockImageCtx { + explicit MockManagedLockImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {} +}; + +namespace watcher { +template <> +struct Traits<MockManagedLockImageCtx> { + typedef librbd::MockImageWatcher Watcher; +}; +} + +struct MockMockManagedLock : public ManagedLock<MockManagedLockImageCtx> { + MockMockManagedLock(librados::IoCtx& ioctx, ContextWQ *work_queue, + const std::string& oid, librbd::MockImageWatcher *watcher, + managed_lock::Mode mode, bool blacklist_on_break_lock, + uint32_t blacklist_expire_seconds) + : ManagedLock<MockManagedLockImageCtx>(ioctx, work_queue, oid, watcher, + librbd::managed_lock::EXCLUSIVE, true, 0) { + }; + virtual ~MockMockManagedLock() = default; + + MOCK_METHOD2(post_reacquire_lock_handler, void(int, Context*)); + + MOCK_METHOD2(pre_release_lock_handler, void(bool, Context*)); + MOCK_METHOD3(post_release_lock_handler, void(bool, int, Context*)); +}; + +namespace managed_lock { + +template<typename T> +struct BaseRequest { + static std::list<T *> s_requests; + Context *on_finish = nullptr; + + static T* create(librados::IoCtx& ioctx, MockImageWatcher *watcher, + ContextWQ *work_queue, const std::string& oid, + const std::string& cookie, Context *on_finish) { + ceph_assert(!s_requests.empty()); + T* req = s_requests.front(); + req->on_finish = on_finish; + s_requests.pop_front(); + return req; + } + + BaseRequest() { + s_requests.push_back(reinterpret_cast<T*>(this)); + } +}; + +template<typename T> +std::list<T *> BaseRequest<T>::s_requests; + +template <> +struct AcquireRequest<MockManagedLockImageCtx> : public BaseRequest<AcquireRequest<MockManagedLockImageCtx> > { + static AcquireRequest* create(librados::IoCtx& ioctx, + MockImageWatcher *watcher, + ContextWQ *work_queue, const std::string& oid, + const std::string& cookie, + bool exclusive, bool blacklist_on_break_lock, + uint32_t blacklist_expire_seconds, + Context *on_finish) { + return BaseRequest::create(ioctx, watcher, work_queue, oid, cookie, on_finish); + } + + MOCK_METHOD0(send, void()); +}; + +template <> +struct ReacquireRequest<MockManagedLockImageCtx> : public BaseRequest<ReacquireRequest<MockManagedLockImageCtx> > { + static ReacquireRequest* create(librados::IoCtx &ioctx, const std::string& oid, + const string& old_cookie, const std::string& new_cookie, + bool exclusive, Context *on_finish) { + return BaseRequest::create(ioctx, nullptr, nullptr, oid, new_cookie, + on_finish); + } + + MOCK_METHOD0(send, void()); +}; + +template <> +struct ReleaseRequest<MockManagedLockImageCtx> : public BaseRequest<ReleaseRequest<MockManagedLockImageCtx> > { + static ReleaseRequest* create(librados::IoCtx& ioctx, MockImageWatcher *watcher, + ContextWQ *work_queue, const std::string& oid, + const std::string& cookie, Context *on_finish) { + return BaseRequest::create(ioctx, watcher, work_queue, oid, cookie, + on_finish); + } + MOCK_METHOD0(send, void()); +}; + +template <> +struct GetLockerRequest<MockManagedLockImageCtx> { + static GetLockerRequest* create(librados::IoCtx& ioctx, + const std::string& oid, bool exclusive, + Locker *locker, Context *on_finish) { + ceph_abort_msg("unexpected call"); + } + + void send() { + ceph_abort_msg("unexpected call"); + } +}; + +template <> +struct BreakRequest<MockManagedLockImageCtx> { + static BreakRequest* create(librados::IoCtx& ioctx, ContextWQ *work_queue, + const std::string& oid, const Locker &locker, + bool exclusive, bool blacklist_locker, + uint32_t blacklist_expire_seconds, + bool force_break_lock, Context *on_finish) { + ceph_abort_msg("unexpected call"); + } + + void send() { + ceph_abort_msg("unexpected call"); + } +}; + +} // namespace managed_lock +} // namespace librbd + +// template definitions +#include "librbd/ManagedLock.cc" +template class librbd::ManagedLock<librbd::MockManagedLockImageCtx>; + + +ACTION_P3(QueueRequest, request, r, wq) { + if (request->on_finish != nullptr) { + if (wq != nullptr) { + wq->queue(request->on_finish, r); + } else { + request->on_finish->complete(r); + } + } +} + +ACTION_P2(QueueContext, r, wq) { + wq->queue(arg0, r); +} + +ACTION_P(Notify, ctx) { + ctx->complete(0); +} + +namespace librbd { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::WithArg; + +class TestMockManagedLock : public TestMockFixture { +public: + typedef ManagedLock<MockManagedLockImageCtx> MockManagedLock; + typedef managed_lock::AcquireRequest<MockManagedLockImageCtx> MockAcquireRequest; + typedef managed_lock::ReacquireRequest<MockManagedLockImageCtx> MockReacquireRequest; + typedef managed_lock::ReleaseRequest<MockManagedLockImageCtx> MockReleaseRequest; + + void expect_get_watch_handle(MockImageWatcher &mock_watcher, + uint64_t watch_handle = 1234567890) { + EXPECT_CALL(mock_watcher, get_watch_handle()) + .WillOnce(Return(watch_handle)); + } + + void expect_acquire_lock(MockImageWatcher &watcher, + ContextWQ *work_queue, + MockAcquireRequest &acquire_request, int r) { + expect_get_watch_handle(watcher); + EXPECT_CALL(acquire_request, send()) + .WillOnce(QueueRequest(&acquire_request, r, work_queue)); + } + + void expect_release_lock(ContextWQ *work_queue, + MockReleaseRequest &release_request, int r) { + EXPECT_CALL(release_request, send()) + .WillOnce(QueueRequest(&release_request, r, work_queue)); + } + + void expect_reacquire_lock(MockImageWatcher& watcher, + ContextWQ *work_queue, + MockReacquireRequest &mock_reacquire_request, + int r) { + EXPECT_CALL(mock_reacquire_request, send()) + .WillOnce(QueueRequest(&mock_reacquire_request, r, work_queue)); + } + + void expect_flush_notifies(MockImageWatcher *mock_watcher) { + EXPECT_CALL(*mock_watcher, flush(_)) + .WillOnce(CompleteContext(0, (ContextWQ *)nullptr)); + } + + void expect_post_reacquired_lock_handler(MockImageWatcher& watcher, + MockMockManagedLock &managed_lock, uint64_t &client_id) { + expect_get_watch_handle(watcher); + EXPECT_CALL(managed_lock, post_reacquire_lock_handler(_, _)) + .WillOnce(Invoke([&client_id](int r, Context *on_finish){ + if (r >= 0) { + client_id = 98765; + } + on_finish->complete(r);})); + } + + void expect_pre_release_lock_handler(MockMockManagedLock &managed_lock, + bool shutting_down, int r) { + EXPECT_CALL(managed_lock, pre_release_lock_handler(shutting_down, _)) + .WillOnce(WithArg<1>(Invoke([r](Context *on_finish){ + on_finish->complete(r); + }))); + } + + void expect_post_release_lock_handler(MockMockManagedLock &managed_lock, + bool shutting_down, int expect_r, + int r) { + EXPECT_CALL(managed_lock, post_release_lock_handler(shutting_down, expect_r, _)) + .WillOnce(WithArg<2>(Invoke([r](Context *on_finish){ + on_finish->complete(r); + }))); + } + + int when_acquire_lock(MockManagedLock &managed_lock) { + C_SaferCond ctx; + { + managed_lock.acquire_lock(&ctx); + } + return ctx.wait(); + } + int when_release_lock(MockManagedLock &managed_lock) { + C_SaferCond ctx; + { + managed_lock.release_lock(&ctx); + } + return ctx.wait(); + } + int when_shut_down(MockManagedLock &managed_lock) { + C_SaferCond ctx; + { + managed_lock.shut_down(&ctx); + } + return ctx.wait(); + } + + bool is_lock_owner(MockManagedLock &managed_lock) { + return managed_lock.is_lock_owner(); + } +}; + +TEST_F(TestMockManagedLock, StateTransitions) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest request_lock_acquire1; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, request_lock_acquire1, 0); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + ASSERT_TRUE(is_lock_owner(managed_lock)); + + MockReleaseRequest request_release; + expect_release_lock(ictx->op_work_queue, request_release, 0); + ASSERT_EQ(0, when_release_lock(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); + + MockAcquireRequest request_lock_acquire2; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, request_lock_acquire2, 0); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + ASSERT_TRUE(is_lock_owner(managed_lock)); + + MockReleaseRequest shutdown_release; + expect_release_lock(ictx->op_work_queue, shutdown_release, 0); + ASSERT_EQ(0, when_shut_down(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); +} + +TEST_F(TestMockManagedLock, AcquireLockLockedState) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest try_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, try_lock_acquire, 0); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + + MockReleaseRequest shutdown_release; + expect_release_lock(ictx->op_work_queue, shutdown_release, 0); + ASSERT_EQ(0, when_shut_down(managed_lock)); +} + +TEST_F(TestMockManagedLock, AcquireLockAlreadyLocked) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest try_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, try_lock_acquire, -EAGAIN); + ASSERT_EQ(-EAGAIN, when_acquire_lock(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); + + ASSERT_EQ(0, when_shut_down(managed_lock)); +} + +TEST_F(TestMockManagedLock, AcquireLockBusy) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest try_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, try_lock_acquire, -EBUSY); + ASSERT_EQ(-EBUSY, when_acquire_lock(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); + + ASSERT_EQ(0, when_shut_down(managed_lock)); +} + +TEST_F(TestMockManagedLock, AcquireLockError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest try_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, try_lock_acquire, -EINVAL); + + ASSERT_EQ(-EINVAL, when_acquire_lock(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); + + ASSERT_EQ(0, when_shut_down(managed_lock)); +} + +TEST_F(TestMockManagedLock, AcquireLockBlacklist) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + // will abort after seeing blacklist error (avoid infinite request loop) + MockAcquireRequest request_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, request_lock_acquire, -EBLACKLISTED); + ASSERT_EQ(-EBLACKLISTED, when_acquire_lock(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); + + ASSERT_EQ(0, when_shut_down(managed_lock)); +} + +TEST_F(TestMockManagedLock, ReleaseLockUnlockedState) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + ASSERT_EQ(0, when_release_lock(managed_lock)); + + ASSERT_EQ(0, when_shut_down(managed_lock)); +} + +TEST_F(TestMockManagedLock, ReleaseLockBlacklist) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockMockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest try_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, try_lock_acquire, 0); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + + expect_pre_release_lock_handler(managed_lock, false, -EBLACKLISTED); + expect_post_release_lock_handler(managed_lock, false, -EBLACKLISTED, -EBLACKLISTED); + ASSERT_EQ(-EBLACKLISTED, when_release_lock(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); + + ASSERT_EQ(0, when_shut_down(managed_lock)); +} + +TEST_F(TestMockManagedLock, ReleaseLockError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest try_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, try_lock_acquire, 0); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + + MockReleaseRequest release; + expect_release_lock(ictx->op_work_queue, release, -EINVAL); + + ASSERT_EQ(-EINVAL, when_release_lock(managed_lock)); + ASSERT_TRUE(is_lock_owner(managed_lock)); + + MockReleaseRequest shutdown_release; + expect_release_lock(ictx->op_work_queue, shutdown_release, 0); + ASSERT_EQ(0, when_shut_down(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); +} + +TEST_F(TestMockManagedLock, ConcurrentRequests) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + expect_get_watch_handle(*mock_image_ctx.image_watcher); + + C_SaferCond wait_for_send_ctx1; + MockAcquireRequest acquire_error; + EXPECT_CALL(acquire_error, send()) + .WillOnce(Notify(&wait_for_send_ctx1)); + + MockAcquireRequest request_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, request_acquire, 0); + + MockReleaseRequest release; + C_SaferCond wait_for_send_ctx2; + EXPECT_CALL(release, send()) + .WillOnce(Notify(&wait_for_send_ctx2)); + + C_SaferCond acquire_request_ctx1; + managed_lock.acquire_lock(&acquire_request_ctx1); + + C_SaferCond acquire_lock_ctx1; + C_SaferCond acquire_lock_ctx2; + managed_lock.acquire_lock(&acquire_lock_ctx1); + managed_lock.acquire_lock(&acquire_lock_ctx2); + + // fail the try_lock + ASSERT_EQ(0, wait_for_send_ctx1.wait()); + acquire_error.on_finish->complete(-EINVAL); + ASSERT_EQ(-EINVAL, acquire_request_ctx1.wait()); + + C_SaferCond acquire_lock_ctx3; + managed_lock.acquire_lock(&acquire_lock_ctx3); + + C_SaferCond release_lock_ctx1; + managed_lock.release_lock(&release_lock_ctx1); + + // all three pending request locks should complete + ASSERT_EQ(-EINVAL, acquire_lock_ctx1.wait()); + ASSERT_EQ(-EINVAL, acquire_lock_ctx2.wait()); + ASSERT_EQ(0, acquire_lock_ctx3.wait()); + + // proceed with the release + ASSERT_EQ(0, wait_for_send_ctx2.wait()); + release.on_finish->complete(0); + ASSERT_EQ(0, release_lock_ctx1.wait()); + + ASSERT_EQ(0, when_shut_down(managed_lock)); +} + +TEST_F(TestMockManagedLock, ReacquireLock) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest request_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, request_lock_acquire, 0); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + ASSERT_TRUE(is_lock_owner(managed_lock)); + + MockReacquireRequest mock_reacquire_request; + C_SaferCond reacquire_ctx; + expect_get_watch_handle(*mock_image_ctx.image_watcher, 98765); + expect_reacquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, mock_reacquire_request, 0); + managed_lock.reacquire_lock(&reacquire_ctx); + ASSERT_EQ(0, reacquire_ctx.wait()); + + MockReleaseRequest shutdown_release; + expect_release_lock(ictx->op_work_queue, shutdown_release, 0); + ASSERT_EQ(0, when_shut_down(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); +} + +TEST_F(TestMockManagedLock, AttemptReacquireBlacklistedLock) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest request_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, + request_lock_acquire, 0); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + ASSERT_TRUE(is_lock_owner(managed_lock)); + + expect_get_watch_handle(*mock_image_ctx.image_watcher, 0); + + MockReleaseRequest request_release; + expect_release_lock(ictx->op_work_queue, request_release, 0); + + expect_get_watch_handle(*mock_image_ctx.image_watcher, 0); + + managed_lock.reacquire_lock(nullptr); + + ASSERT_EQ(0, when_shut_down(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); +} + +TEST_F(TestMockManagedLock, ReacquireBlacklistedLock) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest request_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, + request_lock_acquire, 0); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + ASSERT_TRUE(is_lock_owner(managed_lock)); + + expect_get_watch_handle(*mock_image_ctx.image_watcher, 0); + + MockReleaseRequest request_release; + expect_release_lock(ictx->op_work_queue, request_release, 0); + + MockAcquireRequest request_lock_reacquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, + request_lock_reacquire, 0); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + ASSERT_TRUE(is_lock_owner(managed_lock)); + + C_SaferCond reacquire_ctx; + managed_lock.reacquire_lock(&reacquire_ctx); + ASSERT_EQ(0, reacquire_ctx.wait()); + + MockReleaseRequest shutdown_release; + expect_release_lock(ictx->op_work_queue, shutdown_release, 0); + ASSERT_EQ(0, when_shut_down(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); +} + +TEST_F(TestMockManagedLock, ReacquireLockError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest request_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, request_lock_acquire, 0); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + ASSERT_TRUE(is_lock_owner(managed_lock)); + + MockReacquireRequest mock_reacquire_request; + C_SaferCond reacquire_ctx; + expect_get_watch_handle(*mock_image_ctx.image_watcher, 98765); + expect_reacquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, mock_reacquire_request, -EOPNOTSUPP); + + MockReleaseRequest reacquire_lock_release; + expect_release_lock(ictx->op_work_queue, reacquire_lock_release, 0); + + MockAcquireRequest reacquire_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, reacquire_lock_acquire, 0); + + managed_lock.reacquire_lock(&reacquire_ctx); + ASSERT_EQ(0, reacquire_ctx.wait()); + + MockReleaseRequest shutdown_release; + expect_release_lock(ictx->op_work_queue, shutdown_release, 0); + ASSERT_EQ(0, when_shut_down(managed_lock)); + ASSERT_FALSE(is_lock_owner(managed_lock)); +} + +TEST_F(TestMockManagedLock, ReacquireWithSameCookie) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockMockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + InSequence seq; + + MockAcquireRequest request_lock_acquire; + expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, request_lock_acquire, 0); + ASSERT_EQ(0, when_acquire_lock(managed_lock)); + ASSERT_TRUE(is_lock_owner(managed_lock)); + + // watcher with same cookie after rewatch + uint64_t client_id = 0; + C_SaferCond reacquire_ctx; + expect_post_reacquired_lock_handler(*mock_image_ctx.image_watcher, managed_lock, client_id); + managed_lock.reacquire_lock(&reacquire_ctx); + ASSERT_LT(0U, client_id); + ASSERT_TRUE(is_lock_owner(managed_lock)); + + MockReleaseRequest shutdown_release; + expect_pre_release_lock_handler(managed_lock, true, 0); + expect_release_lock(ictx->op_work_queue, shutdown_release, 0); + expect_post_release_lock_handler(managed_lock, true, 0, 0); + ASSERT_EQ(0, when_shut_down(managed_lock)); +} + +TEST_F(TestMockManagedLock, ShutDownWhileWaiting) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockManagedLockImageCtx mock_image_ctx(*ictx); + MockMockManagedLock managed_lock(ictx->md_ctx, ictx->op_work_queue, + ictx->header_oid, mock_image_ctx.image_watcher, + librbd::managed_lock::EXCLUSIVE, true, 0); + + InSequence seq; + + expect_get_watch_handle(*mock_image_ctx.image_watcher, 0); + + C_SaferCond acquire_ctx; + managed_lock.acquire_lock(&acquire_ctx); + + ASSERT_EQ(0, when_shut_down(managed_lock)); + ASSERT_EQ(-ESHUTDOWN, acquire_ctx.wait()); + ASSERT_FALSE(is_lock_owner(managed_lock)); +} + +} // namespace librbd diff --git a/src/test/librbd/test_mock_ObjectMap.cc b/src/test/librbd/test_mock_ObjectMap.cc new file mode 100644 index 00000000..41672c73 --- /dev/null +++ b/src/test/librbd/test_mock_ObjectMap.cc @@ -0,0 +1,278 @@ +// -*- 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 "librbd/ObjectMap.h" +#include "librbd/object_map/RefreshRequest.h" +#include "librbd/object_map/UnlockRequest.h" +#include "librbd/object_map/UpdateRequest.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace object_map { + +template <> +struct RefreshRequest<MockTestImageCtx> { + Context *on_finish = nullptr; + ceph::BitVector<2u> *object_map = nullptr; + static RefreshRequest *s_instance; + static RefreshRequest *create(MockTestImageCtx &image_ctx, + ceph::BitVector<2u> *object_map, + uint64_t snap_id, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + s_instance->object_map = object_map; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + RefreshRequest() { + s_instance = this; + } +}; + +template <> +struct UnlockRequest<MockTestImageCtx> { + Context *on_finish = nullptr; + static UnlockRequest *s_instance; + static UnlockRequest *create(MockTestImageCtx &image_ctx, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + UnlockRequest() { + s_instance = this; + } +}; + +template <> +struct UpdateRequest<MockTestImageCtx> { + Context *on_finish = nullptr; + static UpdateRequest *s_instance; + static UpdateRequest *create(MockTestImageCtx &image_ctx, + ceph::BitVector<2u> *object_map, + uint64_t snap_id, + uint64_t start_object_no, uint64_t end_object_no, + uint8_t new_state, + const boost::optional<uint8_t> ¤t_state, + const ZTracer::Trace &parent_trace, + bool ignore_enoent, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + s_instance->construct(snap_id, start_object_no, end_object_no, new_state, + current_state, ignore_enoent); + return s_instance; + } + + MOCK_METHOD6(construct, void(uint64_t snap_id, uint64_t start_object_no, + uint64_t end_object_no, uint8_t new_state, + const boost::optional<uint8_t> ¤t_state, + bool ignore_enoent)); + MOCK_METHOD0(send, void()); + UpdateRequest() { + s_instance = this; + } +}; + +RefreshRequest<MockTestImageCtx> *RefreshRequest<MockTestImageCtx>::s_instance = nullptr; +UnlockRequest<MockTestImageCtx> *UnlockRequest<MockTestImageCtx>::s_instance = nullptr; +UpdateRequest<MockTestImageCtx> *UpdateRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace object_map +} // namespace librbd + +#include "librbd/ObjectMap.cc" + +namespace librbd { + +using testing::_; +using testing::InSequence; +using testing::Invoke; + +class TestMockObjectMap : public TestMockFixture { +public: + typedef ObjectMap<MockTestImageCtx> MockObjectMap; + typedef object_map::RefreshRequest<MockTestImageCtx> MockRefreshRequest; + typedef object_map::UnlockRequest<MockTestImageCtx> MockUnlockRequest; + typedef object_map::UpdateRequest<MockTestImageCtx> MockUpdateRequest; + + void expect_refresh(MockTestImageCtx &mock_image_ctx, + MockRefreshRequest &mock_refresh_request, + const ceph::BitVector<2u> &object_map, int r) { + EXPECT_CALL(mock_refresh_request, send()) + .WillOnce(Invoke([&mock_image_ctx, &mock_refresh_request, &object_map, r]() { + *mock_refresh_request.object_map = object_map; + mock_image_ctx.image_ctx->op_work_queue->queue(mock_refresh_request.on_finish, r); + })); + } + + void expect_unlock(MockTestImageCtx &mock_image_ctx, + MockUnlockRequest &mock_unlock_request, int r) { + EXPECT_CALL(mock_unlock_request, send()) + .WillOnce(Invoke([&mock_image_ctx, &mock_unlock_request, r]() { + mock_image_ctx.image_ctx->op_work_queue->queue(mock_unlock_request.on_finish, r); + })); + } + + void expect_update(MockTestImageCtx &mock_image_ctx, + MockUpdateRequest &mock_update_request, + uint64_t snap_id, uint64_t start_object_no, + uint64_t end_object_no, uint8_t new_state, + const boost::optional<uint8_t> ¤t_state, + bool ignore_enoent, Context **on_finish) { + EXPECT_CALL(mock_update_request, construct(snap_id, start_object_no, + end_object_no, new_state, + current_state, ignore_enoent)) + .Times(1); + EXPECT_CALL(mock_update_request, send()) + .WillOnce(Invoke([&mock_update_request, on_finish]() { + *on_finish = mock_update_request.on_finish; + })); + } + +}; + +TEST_F(TestMockObjectMap, NonDetainedUpdate) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + InSequence seq; + ceph::BitVector<2u> object_map; + object_map.resize(4); + MockRefreshRequest mock_refresh_request; + expect_refresh(mock_image_ctx, mock_refresh_request, object_map, 0); + + MockUpdateRequest mock_update_request; + Context *finish_update_1; + expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, + 0, 1, 1, {}, false, &finish_update_1); + Context *finish_update_2; + expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, + 1, 2, 1, {}, false, &finish_update_2); + + MockUnlockRequest mock_unlock_request; + expect_unlock(mock_image_ctx, mock_unlock_request, 0); + + MockObjectMap mock_object_map(mock_image_ctx, CEPH_NOSNAP); + C_SaferCond open_ctx; + mock_object_map.open(&open_ctx); + ASSERT_EQ(0, open_ctx.wait()); + + C_SaferCond update_ctx1; + C_SaferCond update_ctx2; + { + RWLock::RLocker snap_locker(mock_image_ctx.snap_lock); + RWLock::WLocker object_map_locker(mock_image_ctx.object_map_lock); + mock_object_map.aio_update(CEPH_NOSNAP, 0, 1, {}, {}, false, &update_ctx1); + mock_object_map.aio_update(CEPH_NOSNAP, 1, 1, {}, {}, false, &update_ctx2); + } + + finish_update_2->complete(0); + ASSERT_EQ(0, update_ctx2.wait()); + + finish_update_1->complete(0); + ASSERT_EQ(0, update_ctx1.wait()); + + C_SaferCond close_ctx; + mock_object_map.close(&close_ctx); + ASSERT_EQ(0, close_ctx.wait()); +} + +TEST_F(TestMockObjectMap, DetainedUpdate) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + InSequence seq; + ceph::BitVector<2u> object_map; + object_map.resize(4); + MockRefreshRequest mock_refresh_request; + expect_refresh(mock_image_ctx, mock_refresh_request, object_map, 0); + + MockUpdateRequest mock_update_request; + Context *finish_update_1; + expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, + 1, 4, 1, {}, false, &finish_update_1); + Context *finish_update_2 = nullptr; + expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, + 1, 3, 1, {}, false, &finish_update_2); + Context *finish_update_3 = nullptr; + expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, + 2, 3, 1, {}, false, &finish_update_3); + Context *finish_update_4 = nullptr; + expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, + 0, 2, 1, {}, false, &finish_update_4); + + MockUnlockRequest mock_unlock_request; + expect_unlock(mock_image_ctx, mock_unlock_request, 0); + + MockObjectMap mock_object_map(mock_image_ctx, CEPH_NOSNAP); + C_SaferCond open_ctx; + mock_object_map.open(&open_ctx); + ASSERT_EQ(0, open_ctx.wait()); + + C_SaferCond update_ctx1; + C_SaferCond update_ctx2; + C_SaferCond update_ctx3; + C_SaferCond update_ctx4; + { + RWLock::RLocker snap_locker(mock_image_ctx.snap_lock); + RWLock::WLocker object_map_locker(mock_image_ctx.object_map_lock); + mock_object_map.aio_update(CEPH_NOSNAP, 1, 4, 1, {}, {}, false, + &update_ctx1); + mock_object_map.aio_update(CEPH_NOSNAP, 1, 3, 1, {}, {}, false, + &update_ctx2); + mock_object_map.aio_update(CEPH_NOSNAP, 2, 3, 1, {}, {}, false, + &update_ctx3); + mock_object_map.aio_update(CEPH_NOSNAP, 0, 2, 1, {}, {}, false, + &update_ctx4); + } + + // updates 2, 3, and 4 are blocked on update 1 + ASSERT_EQ(nullptr, finish_update_2); + finish_update_1->complete(0); + ASSERT_EQ(0, update_ctx1.wait()); + + // updates 3 and 4 are blocked on update 2 + ASSERT_NE(nullptr, finish_update_2); + ASSERT_EQ(nullptr, finish_update_3); + ASSERT_EQ(nullptr, finish_update_4); + finish_update_2->complete(0); + ASSERT_EQ(0, update_ctx2.wait()); + + ASSERT_NE(nullptr, finish_update_3); + ASSERT_NE(nullptr, finish_update_4); + finish_update_3->complete(0); + finish_update_4->complete(0); + ASSERT_EQ(0, update_ctx3.wait()); + ASSERT_EQ(0, update_ctx4.wait()); + + C_SaferCond close_ctx; + mock_object_map.close(&close_ctx); + ASSERT_EQ(0, close_ctx.wait()); +} + +} // namespace librbd + diff --git a/src/test/librbd/test_mock_TrashWatcher.cc b/src/test/librbd/test_mock_TrashWatcher.cc new file mode 100644 index 00000000..d7f3c679 --- /dev/null +++ b/src/test/librbd/test_mock_TrashWatcher.cc @@ -0,0 +1,95 @@ +// -*- 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 "include/rbd_types.h" +#include "librbd/TrashWatcher.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include <list> + +namespace librbd { +namespace { + +struct MockTrashWatcher : public TrashWatcher<> { + MockTrashWatcher(ImageCtx &image_ctx) + : TrashWatcher<>(image_ctx.md_ctx, image_ctx.op_work_queue) { + } + + MOCK_METHOD2(handle_image_added, void(const std::string&, + const cls::rbd::TrashImageSpec&)); + MOCK_METHOD1(handle_image_removed, void(const std::string&)); +}; + +} // anonymous namespace + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::StrEq; + +class TestTrashWatcher : public TestMockFixture { +public: + void SetUp() override { + TestFixture::SetUp(); + + bufferlist bl; + ASSERT_EQ(0, m_ioctx.write_full(RBD_TRASH, bl)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + m_trash_watcher = new MockTrashWatcher(*ictx); + + C_SaferCond ctx; + m_trash_watcher->register_watch(&ctx); + if (ctx.wait() != 0) { + delete m_trash_watcher; + m_trash_watcher = nullptr; + FAIL(); + } + } + + void TearDown() override { + if (m_trash_watcher != nullptr) { + C_SaferCond ctx; + m_trash_watcher->unregister_watch(&ctx); + ASSERT_EQ(0, ctx.wait()); + delete m_trash_watcher; + } + + TestFixture::TearDown(); + } + + MockTrashWatcher *m_trash_watcher = nullptr; +}; + +TEST_F(TestTrashWatcher, ImageAdded) { + REQUIRE_FORMAT_V2(); + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_USER, "image name", + ceph_clock_now(), ceph_clock_now()}; + + EXPECT_CALL(*m_trash_watcher, handle_image_added(StrEq("image id"), + trash_image_spec)) + .Times(AtLeast(1)); + + C_SaferCond ctx; + MockTrashWatcher::notify_image_added(m_ioctx, "image id", trash_image_spec, + &ctx); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestTrashWatcher, ImageRemoved) { + REQUIRE_FORMAT_V2(); + + EXPECT_CALL(*m_trash_watcher, handle_image_removed(StrEq("image id"))) + .Times(AtLeast(1)); + + C_SaferCond ctx; + MockTrashWatcher::notify_image_removed(m_ioctx, "image id", &ctx); + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace librbd diff --git a/src/test/librbd/test_mock_Watcher.cc b/src/test/librbd/test_mock_Watcher.cc new file mode 100644 index 00000000..529a56ab --- /dev/null +++ b/src/test/librbd/test_mock_Watcher.cc @@ -0,0 +1,404 @@ +// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "common/Cond.h" +#include "common/Mutex.h" +#include "librados/AioCompletionImpl.h" +#include "librbd/Watcher.h" +#include "librbd/watcher/RewatchRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <list> + +namespace librbd { + +namespace { + +struct MockWatcher : public Watcher { + std::string oid; + + MockWatcher(librados::IoCtx& ioctx, ContextWQ *work_queue, + const std::string& oid) + : Watcher(ioctx, work_queue, oid) { + } + + virtual void handle_notify(uint64_t notify_id, uint64_t handle, + uint64_t notifier_id, bufferlist &bl) { + } +}; + +} // anonymous namespace +} // namespace librbd + +namespace librbd { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::WithArg; +using ::testing::WithArgs; + +class TestMockWatcher : public TestMockFixture { +public: + TestMockWatcher() : m_lock("TestMockWatcher::m_lock") { + } + + virtual void SetUp() { + TestMockFixture::SetUp(); + + m_oid = get_temp_image_name(); + + bufferlist bl; + ASSERT_EQ(0, m_ioctx.write_full(m_oid, bl)); + } + + void expect_aio_watch(MockImageCtx &mock_image_ctx, int r, + const std::function<void()> &action = std::function<void()>()) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + librados::MockTestMemRadosClient *mock_rados_client( + mock_io_ctx.get_mock_rados_client()); + + EXPECT_CALL(mock_io_ctx, aio_watch(m_oid, _, _, _)) + .WillOnce(DoAll(WithArgs<1, 2, 3>(Invoke([this, &mock_image_ctx, mock_rados_client, r, action]( + librados::AioCompletionImpl *c, uint64_t *cookie, + librados::WatchCtx2 *watch_ctx) { + if (r == 0) { + *cookie = 234U; + m_watch_ctx = watch_ctx; + } + + c->get(); + mock_image_ctx.image_ctx->op_work_queue->queue(new FunctionContext([mock_rados_client, action, c](int r) { + if (action) { + action(); + } + + mock_rados_client->finish_aio_completion(c, r); + }), r); + notify_watch(); + })), Return(0))); + } + + void expect_aio_unwatch(MockImageCtx &mock_image_ctx, int r, + const std::function<void()> &action = std::function<void()>()) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + librados::MockTestMemRadosClient *mock_rados_client( + mock_io_ctx.get_mock_rados_client()); + + EXPECT_CALL(mock_io_ctx, aio_unwatch(_, _)) + .WillOnce(DoAll(Invoke([this, &mock_image_ctx, mock_rados_client, r, action]( + uint64_t handle, librados::AioCompletionImpl *c) { + c->get(); + mock_image_ctx.image_ctx->op_work_queue->queue(new FunctionContext([mock_rados_client, action, c](int r) { + if (action) { + action(); + } + + mock_rados_client->finish_aio_completion(c, r); + }), r); + notify_watch(); + }), Return(0))); + } + + std::string m_oid; + librados::WatchCtx2 *m_watch_ctx = nullptr; + + void notify_watch() { + Mutex::Locker locker(m_lock); + ++m_watch_count; + m_cond.Signal(); + } + + bool wait_for_watch(MockImageCtx &mock_image_ctx, size_t count) { + Mutex::Locker locker(m_lock); + while (m_watch_count < count) { + if (m_cond.WaitInterval(m_lock, utime_t(10, 0)) != 0) { + return false; + } + } + m_watch_count -= count; + return true; + } + + Mutex m_lock; + Cond m_cond; + size_t m_watch_count = 0; +}; + +TEST_F(TestMockWatcher, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, RegisterError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + InSequence seq; + expect_aio_watch(mock_image_ctx, -EINVAL); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(-EINVAL, register_ctx.wait()); +} + +TEST_F(TestMockWatcher, UnregisterError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, -EINVAL); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(-EINVAL, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, Reregister) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + // wait for recovery unwatch/watch + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 3)); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterUnwatchError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, -EINVAL); + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + // wait for recovery unwatch/watch + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 3)); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterWatchError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, -EPERM); + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + // wait for recovery unwatch/watch + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 4)); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterWatchBlacklist) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, -EBLACKLISTED); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 1)); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -EBLACKLISTED); + + // wait for recovery unwatch/watch + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 2)); + ASSERT_TRUE(mock_image_watcher.is_blacklisted()); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterUnwatchPendingUnregister) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + + // inject an unregister + C_SaferCond unregister_ctx; + expect_aio_unwatch(mock_image_ctx, -EBLACKLISTED, + [&mock_image_watcher, &unregister_ctx]() { + mock_image_watcher.unregister_watch(&unregister_ctx); + }); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -EBLACKLISTED); + + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterWatchPendingUnregister) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + // inject an unregister + C_SaferCond unregister_ctx; + expect_aio_watch(mock_image_ctx, -ESHUTDOWN, + [&mock_image_watcher, &unregister_ctx]() { + mock_image_watcher.unregister_watch(&unregister_ctx); + }); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterPendingUnregister) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + // inject an unregister + C_SaferCond unregister_ctx; + expect_aio_watch(mock_image_ctx, 0, + [&mock_image_watcher, &unregister_ctx]() { + mock_image_watcher.unregister_watch(&unregister_ctx); + }); + + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + ASSERT_EQ(0, unregister_ctx.wait()); +} + +} // namespace librbd diff --git a/src/test/librbd/test_mock_fixture.cc b/src/test/librbd/test_mock_fixture.cc new file mode 100644 index 00000000..0a467c16 --- /dev/null +++ b/src/test/librbd/test_mock_fixture.cc @@ -0,0 +1,123 @@ +// -*- 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/mock/MockImageCtx.h" +#include "test/librados_test_stub/LibradosTestStub.h" +#include "test/librados_test_stub/MockTestMemCluster.h" + +// template definitions +#include "librbd/AsyncRequest.cc" +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/operation/Request.cc" + +template class librbd::AsyncRequest<librbd::MockImageCtx>; +template class librbd::AsyncObjectThrottle<librbd::MockImageCtx>; +template class librbd::operation::Request<librbd::MockImageCtx>; + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +TestMockFixture::TestClusterRef TestMockFixture::s_test_cluster; + +void TestMockFixture::SetUpTestCase() { + s_test_cluster = librados_test_stub::get_cluster(); + + // use a mock version of the in-memory cluster + librados_test_stub::set_cluster(boost::shared_ptr<librados::TestCluster>( + new ::testing::NiceMock<librados::MockTestMemCluster>())); + TestFixture::SetUpTestCase(); +} + +void TestMockFixture::TearDownTestCase() { + TestFixture::TearDownTestCase(); + librados_test_stub::set_cluster(s_test_cluster); +} + +void TestMockFixture::TearDown() { + // Mock rados client lives across tests -- reset it to initial state + librados::MockTestMemRadosClient *mock_rados_client = + get_mock_io_ctx(m_ioctx).get_mock_rados_client(); + ASSERT_TRUE(mock_rados_client != nullptr); + + ::testing::Mock::VerifyAndClear(mock_rados_client); + mock_rados_client->default_to_dispatch(); + dynamic_cast<librados::MockTestMemCluster*>( + librados_test_stub::get_cluster().get())->default_to_dispatch(); + + TestFixture::TearDown(); +} + +void TestMockFixture::expect_unlock_exclusive_lock(librbd::ImageCtx &ictx) { + EXPECT_CALL(get_mock_io_ctx(ictx.md_ctx), + exec(_, _, StrEq("lock"), StrEq("unlock"), _, _, _)) + .WillRepeatedly(DoDefault()); +} + +void TestMockFixture::expect_op_work_queue(librbd::MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.op_work_queue, queue(_, _)) + .WillRepeatedly(DispatchContext( + mock_image_ctx.image_ctx->op_work_queue)); +} + +void TestMockFixture::initialize_features(librbd::ImageCtx *ictx, + librbd::MockImageCtx &mock_image_ctx, + librbd::MockExclusiveLock &mock_exclusive_lock, + librbd::MockJournal &mock_journal, + librbd::MockObjectMap &mock_object_map) { + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + mock_image_ctx.journal = &mock_journal; + } + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } +} + +void TestMockFixture::expect_is_journal_appending(librbd::MockJournal &mock_journal, + bool appending) { + EXPECT_CALL(mock_journal, is_journal_appending()).WillOnce(Return(appending)); +} + +void TestMockFixture::expect_is_journal_replaying(librbd::MockJournal &mock_journal) { + EXPECT_CALL(mock_journal, is_journal_replaying()).WillOnce(Return(false)); +} + +void TestMockFixture::expect_is_journal_ready(librbd::MockJournal &mock_journal) { + EXPECT_CALL(mock_journal, is_journal_ready()).WillOnce(Return(true)); +} + +void TestMockFixture::expect_allocate_op_tid(librbd::MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.journal != nullptr) { + EXPECT_CALL(*mock_image_ctx.journal, allocate_op_tid()) + .WillOnce(Return(1U)); + } +} + +void TestMockFixture::expect_append_op_event(librbd::MockImageCtx &mock_image_ctx, + bool can_affect_io, int r) { + if (mock_image_ctx.journal != nullptr) { + if (can_affect_io) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_is_journal_appending(*mock_image_ctx.journal, true); + expect_allocate_op_tid(mock_image_ctx); + EXPECT_CALL(*mock_image_ctx.journal, append_op_event_mock(_, _, _)) + .WillOnce(WithArg<2>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } +} + +void TestMockFixture::expect_commit_op_event(librbd::MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_appending(*mock_image_ctx.journal, true); + expect_is_journal_ready(*mock_image_ctx.journal); + EXPECT_CALL(*mock_image_ctx.journal, commit_op_event(1U, r, _)) + .WillOnce(WithArg<2>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } +} + diff --git a/src/test/librbd/test_mock_fixture.h b/src/test/librbd/test_mock_fixture.h new file mode 100644 index 00000000..408c1b7f --- /dev/null +++ b/src/test/librbd/test_mock_fixture.h @@ -0,0 +1,88 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_TEST_MOCK_FIXTURE_H +#define CEPH_TEST_LIBRBD_TEST_MOCK_FIXTURE_H + +#include "test/librbd/test_fixture.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librados_test_stub/LibradosTestStub.h" +#include "common/WorkQueue.h" +#include <boost/shared_ptr.hpp> +#include <gmock/gmock.h> + +namespace librados { +class TestCluster; +class MockTestMemCluster; +class MockTestMemIoCtxImpl; +class MockTestMemRadosClient; +} +namespace librbd { +class MockImageCtx; +} + +ACTION_P(CopyInBufferlist, str) { + arg0->append(str); +} + +ACTION_P2(CompleteContext, r, wq) { + ContextWQ *context_wq = reinterpret_cast<ContextWQ *>(wq); + if (context_wq != NULL) { + context_wq->queue(arg0, r); + } else { + arg0->complete(r); + } +} + +ACTION_P(DispatchContext, wq) { + wq->queue(arg0, arg1); +} + +ACTION_P3(FinishRequest, request, r, mock) { + librbd::MockImageCtx *mock_image_ctx = + reinterpret_cast<librbd::MockImageCtx *>(mock); + mock_image_ctx->image_ctx->op_work_queue->queue(request->on_finish, r); +} + +ACTION_P(GetReference, ref_object) { + ref_object->get(); +} + +MATCHER_P(ContentsEqual, bl, "") { + // TODO fix const-correctness of bufferlist + return const_cast<bufferlist &>(arg).contents_equal( + const_cast<bufferlist &>(bl)); +} + +class TestMockFixture : public TestFixture { +public: + typedef boost::shared_ptr<librados::TestCluster> TestClusterRef; + + static void SetUpTestCase(); + static void TearDownTestCase(); + + void TearDown() override; + + void expect_op_work_queue(librbd::MockImageCtx &mock_image_ctx); + void expect_unlock_exclusive_lock(librbd::ImageCtx &ictx); + + void initialize_features(librbd::ImageCtx *ictx, + librbd::MockImageCtx &mock_image_ctx, + librbd::MockExclusiveLock &mock_exclusive_lock, + librbd::MockJournal &mock_journal, + librbd::MockObjectMap &mock_object_map); + + void expect_is_journal_appending(librbd::MockJournal &mock_journal, + bool appending); + void expect_is_journal_replaying(librbd::MockJournal &mock_journal); + void expect_is_journal_ready(librbd::MockJournal &mock_journal); + void expect_allocate_op_tid(librbd::MockImageCtx &mock_image_ctx); + void expect_append_op_event(librbd::MockImageCtx &mock_image_ctx, + bool can_affect_io, int r); + void expect_commit_op_event(librbd::MockImageCtx &mock_image_ctx, int r); + +private: + static TestClusterRef s_test_cluster; +}; + +#endif // CEPH_TEST_LIBRBD_TEST_MOCK_FIXTURE_H diff --git a/src/test/librbd/test_notify.py b/src/test/librbd/test_notify.py new file mode 100755 index 00000000..b7b934b5 --- /dev/null +++ b/src/test/librbd/test_notify.py @@ -0,0 +1,180 @@ +#!/usr/bin/python +import os +import sys +import time + +from rados import Rados +from rbd import (RBD, + Image, + ImageNotFound, + RBD_FEATURE_EXCLUSIVE_LOCK, + RBD_FEATURE_LAYERING, + RBD_FEATURE_OBJECT_MAP, + RBD_FEATURE_FAST_DIFF, + RBD_FLAG_OBJECT_MAP_INVALID) + +POOL_NAME='rbd' +PARENT_IMG_NAME='test_notify_parent' +CLONE_IMG_NAME='test_notify_clone' +CLONE_IMG_RENAME='test_notify_clone2' +IMG_SIZE = 16 << 20 +IMG_ORDER = 20 + +def delete_image(ioctx, img_name): + image = Image(ioctx, img_name) + for snap in image.list_snaps(): + snap_name = snap['name'] + print("removing snapshot: %s@%s" % (img_name, snap_name)) + if image.is_protected_snap(snap_name): + image.unprotect_snap(snap_name) + image.remove_snap(snap_name) + image.close() + print("removing image: %s" % img_name) + RBD().remove(ioctx, img_name) + +def safe_delete_image(ioctx, img_name): + try: + delete_image(ioctx, img_name) + except ImageNotFound: + pass + +def get_features(): + features = os.getenv("RBD_FEATURES") + if features is not None: + features = int(features) + else: + features = int(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_LAYERING | + RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF) + assert((features & RBD_FEATURE_EXCLUSIVE_LOCK) != 0) + assert((features & RBD_FEATURE_LAYERING) != 0) + assert((features & RBD_FEATURE_OBJECT_MAP) != 0) + assert((features & RBD_FEATURE_FAST_DIFF) != 0) + return features + +def master(ioctx): + print("starting master") + safe_delete_image(ioctx, CLONE_IMG_RENAME) + safe_delete_image(ioctx, CLONE_IMG_NAME) + safe_delete_image(ioctx, PARENT_IMG_NAME) + + features = get_features() + RBD().create(ioctx, PARENT_IMG_NAME, IMG_SIZE, IMG_ORDER, old_format=False, + features=features) + with Image(ioctx, PARENT_IMG_NAME) as image: + image.create_snap('snap1') + image.protect_snap('snap1') + + features = features & ~(RBD_FEATURE_FAST_DIFF) + RBD().clone(ioctx, PARENT_IMG_NAME, 'snap1', ioctx, CLONE_IMG_NAME, + features=features) + with Image(ioctx, CLONE_IMG_NAME) as image: + print("acquiring exclusive lock") + offset = 0 + data = os.urandom(512) + while offset < IMG_SIZE: + image.write(data, offset) + offset += (1 << IMG_ORDER) + image.write(b'1', IMG_SIZE - 1) + assert(image.is_exclusive_lock_owner()) + + print("waiting for slave to complete") + while image.is_exclusive_lock_owner(): + time.sleep(5) + + safe_delete_image(ioctx, CLONE_IMG_RENAME) + safe_delete_image(ioctx, CLONE_IMG_NAME) + delete_image(ioctx, PARENT_IMG_NAME) + print("finished") + +def slave(ioctx): + print("starting slave") + + while True: + try: + with Image(ioctx, CLONE_IMG_NAME) as image: + if (image.list_lockers() != [] and + image.read(IMG_SIZE - 1, 1) == b'1'): + break + except Exception: + pass + + print("detected master") + + print("rename") + RBD().rename(ioctx, CLONE_IMG_NAME, CLONE_IMG_RENAME); + + with Image(ioctx, CLONE_IMG_RENAME) as image: + print("flatten") + image.flatten() + assert(not image.is_exclusive_lock_owner()) + + print("resize") + image.resize(IMG_SIZE // 2) + assert(not image.is_exclusive_lock_owner()) + assert(image.stat()['size'] == IMG_SIZE // 2) + + print("create_snap") + image.create_snap('snap1') + assert(not image.is_exclusive_lock_owner()) + assert(any(snap['name'] == 'snap1' + for snap in image.list_snaps())) + + print("protect_snap") + image.protect_snap('snap1') + assert(not image.is_exclusive_lock_owner()) + assert(image.is_protected_snap('snap1')) + + print("unprotect_snap") + image.unprotect_snap('snap1') + assert(not image.is_exclusive_lock_owner()) + assert(not image.is_protected_snap('snap1')) + + print("rename_snap") + image.rename_snap('snap1', 'snap1-new') + assert(not image.is_exclusive_lock_owner()) + assert(any(snap['name'] == 'snap1-new' + for snap in image.list_snaps())) + + print("remove_snap") + image.remove_snap('snap1-new') + assert(not image.is_exclusive_lock_owner()) + assert(list(image.list_snaps()) == []) + + print("update_features") + assert((image.features() & RBD_FEATURE_OBJECT_MAP) != 0) + image.update_features(RBD_FEATURE_OBJECT_MAP, False) + assert(not image.is_exclusive_lock_owner()) + assert((image.features() & RBD_FEATURE_OBJECT_MAP) == 0) + image.update_features(RBD_FEATURE_OBJECT_MAP, True) + assert(not image.is_exclusive_lock_owner()) + assert((image.features() & RBD_FEATURE_OBJECT_MAP) != 0) + assert((image.flags() & RBD_FLAG_OBJECT_MAP_INVALID) != 0) + + print("rebuild object map") + image.rebuild_object_map() + assert(not image.is_exclusive_lock_owner()) + assert((image.flags() & RBD_FLAG_OBJECT_MAP_INVALID) == 0) + + print("write") + data = os.urandom(512) + image.write(data, 0) + assert(image.is_exclusive_lock_owner()) + + print("finished") + +def main(): + if len(sys.argv) != 2 or sys.argv[1] not in ['master', 'slave']: + print("usage: %s: [master/slave]" % sys.argv[0]) + sys.exit(2) + + rados = Rados(conffile='') + rados.connect() + ioctx = rados.open_ioctx(POOL_NAME) + if sys.argv[1] == 'master': + master(ioctx) + else: + slave(ioctx) + rados.shutdown() + +if __name__ == "__main__": + main() diff --git a/src/test/librbd/test_support.cc b/src/test/librbd/test_support.cc new file mode 100644 index 00000000..e74068e6 --- /dev/null +++ b/src/test/librbd/test_support.cc @@ -0,0 +1,128 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#include "test/librbd/test_support.h" +#include "include/rbd_types.h" +#include "gtest/gtest.h" +#include <sstream> + +bool get_features(uint64_t *features) { + const char *c = getenv("RBD_FEATURES"); + if (c == NULL) { + return false; + } + + std::stringstream ss(c); + if (!(ss >> *features)) { + return false; + } + return true; +} + +bool is_feature_enabled(uint64_t feature) { + uint64_t features; + return (get_features(&features) && (features & feature) == feature); +} + +int create_image_full_pp(librbd::RBD &rbd, librados::IoCtx &ioctx, + const std::string &name, uint64_t size, + uint64_t features, bool old_format, int *order) +{ + if (old_format) { + librados::Rados rados(ioctx); + int r = rados.conf_set("rbd_default_format", "1"); + if (r < 0) { + return r; + } + return rbd.create(ioctx, name.c_str(), size, order); + } else if ((features & RBD_FEATURE_STRIPINGV2) != 0) { + uint64_t stripe_unit = IMAGE_STRIPE_UNIT; + if (*order) { + // use a conservative stripe_unit for non default order + stripe_unit = (1ull << (*order-1)); + } + + printf("creating image with stripe unit: %" PRIu64 ", stripe count: %" PRIu64 "\n", + stripe_unit, IMAGE_STRIPE_COUNT); + return rbd.create3(ioctx, name.c_str(), size, features, order, stripe_unit, + IMAGE_STRIPE_COUNT); + } else { + return rbd.create2(ioctx, name.c_str(), size, features, order); + } +} + +int create_image_pp(librbd::RBD &rbd, librados::IoCtx &ioctx, + const std::string &name, uint64_t size) { + int order = 0; + uint64_t features = 0; + if (!get_features(&features)) { + // ensure old-format tests actually use the old format + librados::Rados rados(ioctx); + int r = rados.conf_set("rbd_default_format", "1"); + if (r < 0) { + return r; + } + return rbd.create(ioctx, name.c_str(), size, &order); + } else { + return rbd.create2(ioctx, name.c_str(), size, features, &order); + } +} + +int clone_image_pp(librbd::RBD &rbd, librbd::Image &p_image, librados::IoCtx &p_ioctx, + const char *p_name, const char *p_snap_name, librados::IoCtx &c_ioctx, + const char *c_name, uint64_t features) +{ + uint64_t stripe_unit = p_image.get_stripe_unit(); + uint64_t stripe_count = p_image.get_stripe_count(); + + librbd::image_info_t p_info; + int r = p_image.stat(p_info, sizeof(p_info)); + if (r < 0) { + return r; + } + + int c_order = p_info.order; + return rbd.clone2(p_ioctx, p_name, p_snap_name, c_ioctx, c_name, + features, &c_order, stripe_unit, stripe_count); +} + +int get_image_id(librbd::Image &image, std::string *image_id) +{ + int r = image.get_id(image_id); + if (r < 0) { + return r; + } + return 0; +} + +int create_image_data_pool(librados::Rados &rados, std::string &data_pool, bool *created) { + std::string pool; + int r = rados.conf_get("rbd_default_data_pool", pool); + if (r != 0) { + return r; + } else if (pool.empty()) { + return 0; + } + + r = rados.pool_create(pool.c_str()); + if ((r == 0) || (r == -EEXIST)) { + data_pool = pool; + *created = (r == 0); + return 0; + } + + librados::IoCtx ioctx; + r = rados.ioctx_create(pool.c_str(), ioctx); + if (r < 0) { + return r; + } + + librbd::RBD rbd; + return rbd.pool_init(ioctx, true); +} + +bool is_librados_test_stub(librados::Rados &rados) { + std::string fsid; + EXPECT_EQ(0, rados.cluster_fsid(&fsid)); + return fsid == "00000000-1111-2222-3333-444444444444"; +} + diff --git a/src/test/librbd/test_support.h b/src/test/librbd/test_support.h new file mode 100644 index 00000000..abb7c470 --- /dev/null +++ b/src/test/librbd/test_support.h @@ -0,0 +1,38 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#include "include/int_types.h" +#include "include/rados/librados.h" +#include "include/rbd/librbd.hpp" +#include <string> + +static const uint64_t IMAGE_STRIPE_UNIT = 65536; +static const uint64_t IMAGE_STRIPE_COUNT = 16; + +#define TEST_IO_SIZE 512 +#define TEST_IO_TO_SNAP_SIZE 80 + +bool get_features(uint64_t *features); +bool is_feature_enabled(uint64_t feature); +int create_image_pp(librbd::RBD &rbd, librados::IoCtx &ioctx, + const std::string &name, uint64_t size); +int create_image_full_pp(librbd::RBD &rbd, librados::IoCtx &ioctx, + const std::string &name, uint64_t size, + uint64_t features, bool old_format, int *order); +int clone_image_pp(librbd::RBD &rbd, librbd::Image &p_image, librados::IoCtx &p_ioctx, + const char *p_name, const char *p_snap_name, librados::IoCtx &c_ioctx, + const char *c_name, uint64_t features); +int get_image_id(librbd::Image &image, std::string *image_id); +int create_image_data_pool(librados::Rados &rados, std::string &data_pool, bool *created); + +bool is_librados_test_stub(librados::Rados &rados); + +#define REQUIRE(x) { \ + if (!(x)) { \ + std::cout << "SKIPPING" << std::endl; \ + return SUCCEED(); \ + } \ +} + +#define REQUIRE_FEATURE(feature) REQUIRE(is_feature_enabled(feature)) +#define REQUIRE_FORMAT_V1() REQUIRE(!is_feature_enabled(0)) +#define REQUIRE_FORMAT_V2() REQUIRE_FEATURE(0) diff --git a/src/test/librbd/trash/test_mock_MoveRequest.cc b/src/test/librbd/trash/test_mock_MoveRequest.cc new file mode 100644 index 00000000..7bd7dbfc --- /dev/null +++ b/src/test/librbd/trash/test_mock_MoveRequest.cc @@ -0,0 +1,230 @@ +// -*- 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/MockExclusiveLock.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockImageState.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "include/rbd/librbd.hpp" +#include "librbd/Utils.h" +#include "librbd/trash/MoveRequest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + static MockTestImageCtx *s_instance; + static MockTestImageCtx *create(const std::string &image_name, + const std::string &image_id, + const char *snap, librados::IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + s_instance->construct(image_name, image_id); + return s_instance; + } + + MOCK_METHOD2(construct, void(const std::string&, const std::string&)); + + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + s_instance = this; + } +}; + +MockTestImageCtx *MockTestImageCtx::s_instance = nullptr; + +} // anonymous namespace +} // namespace librbd + +#include "librbd/trash/MoveRequest.cc" + +namespace librbd { +namespace trash { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +struct TestMockTrashMoveRequest : public TestMockFixture { + typedef MoveRequest<librbd::MockTestImageCtx> MockMoveRequest; + + void expect_trash_add(MockTestImageCtx &mock_image_ctx, + const std::string& image_id, + cls::rbd::TrashImageSource trash_image_source, + const std::string& name, + const utime_t& end_time, + int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(StrEq("rbd_trash"), _, StrEq("rbd"), StrEq("trash_add"), + _, _, _)) + .WillOnce(WithArg<4>(Invoke([=](bufferlist& in_bl) { + std::string id; + cls::rbd::TrashImageSpec trash_image_spec; + + auto bl_it = in_bl.cbegin(); + decode(id, bl_it); + decode(trash_image_spec, bl_it); + + EXPECT_EQ(id, image_id); + EXPECT_EQ(trash_image_spec.source, + trash_image_source); + EXPECT_EQ(trash_image_spec.name, name); + EXPECT_EQ(trash_image_spec.deferment_end_time, + end_time); + return r; + }))); + } + + void expect_aio_remove(MockTestImageCtx &mock_image_ctx, + const std::string& oid, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), remove(oid, _)) + .WillOnce(Return(r)); + } + + void expect_dir_remove(MockTestImageCtx& mock_image_ctx, + const std::string& name, const std::string& id, + int r) { + bufferlist in_bl; + encode(name, in_bl); + encode(id, in_bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(StrEq("rbd_directory"), _, StrEq("rbd"), StrEq("dir_remove_image"), + ContentsEqual(in_bl), _, _)) + .WillOnce(Return(r)); + } +}; + +TEST_F(TestMockTrashMoveRequest, Success) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + utime_t delete_time{ceph_clock_now()}; + expect_trash_add(mock_image_ctx, "image id", + cls::rbd::TRASH_IMAGE_SOURCE_USER, "image name", delete_time, + 0); + expect_aio_remove(mock_image_ctx, util::id_obj_name("image name"), 0); + expect_dir_remove(mock_image_ctx, "image name", "image id", 0); + + C_SaferCond ctx; + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_USER, "image name", delete_time, + delete_time}; + auto req = MockMoveRequest::create(mock_image_ctx.md_ctx, "image id", + trash_image_spec, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockTrashMoveRequest, TrashAddError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + utime_t delete_time{ceph_clock_now()}; + expect_trash_add(mock_image_ctx, "image id", + cls::rbd::TRASH_IMAGE_SOURCE_USER, "image name", delete_time, + -EPERM); + + C_SaferCond ctx; + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_USER, "image name", delete_time, + delete_time}; + auto req = MockMoveRequest::create(mock_image_ctx.md_ctx, "image id", + trash_image_spec, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockTrashMoveRequest, RemoveIdError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + utime_t delete_time{ceph_clock_now()}; + expect_trash_add(mock_image_ctx, "image id", + cls::rbd::TRASH_IMAGE_SOURCE_USER, "image name", delete_time, + 0); + expect_aio_remove(mock_image_ctx, util::id_obj_name("image name"), -EPERM); + + C_SaferCond ctx; + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_USER, "image name", delete_time, + delete_time}; + auto req = MockMoveRequest::create(mock_image_ctx.md_ctx, "image id", + trash_image_spec, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockTrashMoveRequest, DirectoryRemoveError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + utime_t delete_time{ceph_clock_now()}; + expect_trash_add(mock_image_ctx, "image id", + cls::rbd::TRASH_IMAGE_SOURCE_USER, "image name", delete_time, + 0); + expect_aio_remove(mock_image_ctx, util::id_obj_name("image name"), 0); + expect_dir_remove(mock_image_ctx, "image name", "image id", -EPERM); + + C_SaferCond ctx; + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_USER, "image name", delete_time, + delete_time}; + auto req = MockMoveRequest::create(mock_image_ctx.md_ctx, "image id", + trash_image_spec, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +} // namespace trash +} // namespace librbd diff --git a/src/test/librbd/trash/test_mock_RemoveRequest.cc b/src/test/librbd/trash/test_mock_RemoveRequest.cc new file mode 100644 index 00000000..407855ae --- /dev/null +++ b/src/test/librbd/trash/test_mock_RemoveRequest.cc @@ -0,0 +1,231 @@ +// -*- 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/MockExclusiveLock.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockImageState.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "include/rbd/librbd.hpp" +#include "librbd/Utils.h" +#include "librbd/image/TypeTraits.h" +#include "librbd/image/RemoveRequest.h" +#include "librbd/internal.h" +#include "librbd/trash/RemoveRequest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace image { + +// template <> +// struct TypeTraits<MockTestImageCtx> { +// typedef librbd::MockContextWQ ContextWQ; +// }; + +template <> +class RemoveRequest<MockTestImageCtx> { +private: + typedef ::librbd::image::TypeTraits<MockTestImageCtx> TypeTraits; + typedef typename TypeTraits::ContextWQ ContextWQ; +public: + static RemoveRequest *s_instance; + static RemoveRequest *create(librados::IoCtx &ioctx, + const std::string &image_name, + const std::string &image_id, + bool force, bool from_trash_remove, + ProgressContext &prog_ctx, + ContextWQ *op_work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + static RemoveRequest *create(librados::IoCtx &ioctx, MockTestImageCtx *image_ctx, + bool force, bool from_trash_remove, + ProgressContext &prog_ctx, + ContextWQ *op_work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + RemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +RemoveRequest<MockTestImageCtx> *RemoveRequest<MockTestImageCtx>::s_instance; + +} // namespace image +} // namespace librbd + +#include "librbd/trash/RemoveRequest.cc" + +namespace librbd { +namespace trash { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; + +struct TestMockTrashRemoveRequest : public TestMockFixture { + typedef RemoveRequest<librbd::MockTestImageCtx> MockRemoveRequest; + typedef image::RemoveRequest<librbd::MockTestImageCtx> MockImageRemoveRequest; + + NoOpProgressContext m_prog_ctx; + + void expect_set_state(MockTestImageCtx& mock_image_ctx, + cls::rbd::TrashImageState trash_set_state, + cls::rbd::TrashImageState trash_expect_state, + int r) { + bufferlist in_bl; + encode(mock_image_ctx.id, in_bl); + encode(trash_set_state, in_bl); + encode(trash_expect_state, in_bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(StrEq("rbd_trash"), _, StrEq("rbd"), StrEq("trash_state_set"), + ContentsEqual(in_bl), _, _)) + .WillOnce(Return(r)); + } + + void expect_set_deleting_state(MockTestImageCtx& mock_image_ctx, int r) { + expect_set_state(mock_image_ctx, cls::rbd::TRASH_IMAGE_STATE_REMOVING, + cls::rbd::TRASH_IMAGE_STATE_NORMAL, r); + } + + void expect_restore_normal_state(MockTestImageCtx& mock_image_ctx, int r) { + expect_set_state(mock_image_ctx, cls::rbd::TRASH_IMAGE_STATE_NORMAL, + cls::rbd::TRASH_IMAGE_STATE_REMOVING, r); + } + + void expect_close_image(MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, close(_)) + .WillOnce(Invoke([r](Context *on_finish) { + on_finish->complete(r); + })); + EXPECT_CALL(mock_image_ctx, destroy()); + } + + void expect_remove_image(MockImageRemoveRequest& mock_image_remove_request, + int r) { + EXPECT_CALL(mock_image_remove_request, send()) + .WillOnce(Invoke([&mock_image_remove_request, r]() { + mock_image_remove_request.on_finish->complete(r); + })); + } + + void expect_remove_trash_entry(MockTestImageCtx& mock_image_ctx, + int r) { + bufferlist in_bl; + encode(mock_image_ctx.id, in_bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(StrEq("rbd_trash"), _, StrEq("rbd"), StrEq("trash_remove"), + ContentsEqual(in_bl), _, _)) + .WillOnce(Return(r)); + } +}; + +TEST_F(TestMockTrashRemoveRequest, Success) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageRemoveRequest mock_image_remove_request; + + InSequence seq; + expect_set_deleting_state(mock_image_ctx, 0); + expect_remove_image(mock_image_remove_request, 0); + expect_remove_trash_entry(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockRemoveRequest::create(mock_image_ctx.md_ctx, &mock_image_ctx, + nullptr, false, m_prog_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} +TEST_F(TestMockTrashRemoveRequest, SetDeletingStateError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageRemoveRequest mock_image_remove_request; + + InSequence seq; + expect_set_deleting_state(mock_image_ctx, -EINVAL); + expect_close_image(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockRemoveRequest::create(mock_image_ctx.md_ctx, &mock_image_ctx, + nullptr, false, m_prog_ctx, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockTrashRemoveRequest, RemoveImageError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageRemoveRequest mock_image_remove_request; + + InSequence seq; + expect_set_deleting_state(mock_image_ctx, 0); + expect_remove_image(mock_image_remove_request, -EINVAL); + expect_restore_normal_state(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockRemoveRequest::create(mock_image_ctx.md_ctx, &mock_image_ctx, + nullptr, false, m_prog_ctx, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockTrashRemoveRequest, RemoveTrashEntryError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageRemoveRequest mock_image_remove_request; + + InSequence seq; + expect_set_deleting_state(mock_image_ctx, 0); + expect_remove_image(mock_image_remove_request, 0); + expect_remove_trash_entry(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockRemoveRequest::create(mock_image_ctx.md_ctx, &mock_image_ctx, + nullptr, false, m_prog_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace trash +} // namespace librbd diff --git a/src/test/librbd/watcher/test_mock_RewatchRequest.cc b/src/test/librbd/watcher/test_mock_RewatchRequest.cc new file mode 100644 index 00000000..58069d0a --- /dev/null +++ b/src/test/librbd/watcher/test_mock_RewatchRequest.cc @@ -0,0 +1,229 @@ +// -*- 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 "include/rados/librados.hpp" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "librados/AioCompletionImpl.h" +#include "librbd/watcher/RewatchRequest.h" + +namespace librbd { +namespace watcher { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::WithArgs; + +struct TestMockWatcherRewatchRequest : public TestMockFixture { + typedef RewatchRequest MockRewatchRequest; + + TestMockWatcherRewatchRequest() + : m_watch_lock("watch_lock") { + } + + void expect_aio_watch(MockImageCtx &mock_image_ctx, int r) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx( + mock_image_ctx.md_ctx)); + + EXPECT_CALL(mock_io_ctx, aio_watch(mock_image_ctx.header_oid, _, _, _)) + .WillOnce(DoAll(WithArgs<1, 2>(Invoke([&mock_image_ctx, &mock_io_ctx, r](librados::AioCompletionImpl *c, uint64_t *cookie) { + *cookie = 234; + c->get(); + mock_image_ctx.image_ctx->op_work_queue->queue(new FunctionContext([&mock_io_ctx, c](int r) { + mock_io_ctx.get_mock_rados_client()->finish_aio_completion(c, r); + }), r); + })), + Return(0))); + } + + void expect_aio_unwatch(MockImageCtx &mock_image_ctx, int r) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx( + mock_image_ctx.md_ctx)); + + EXPECT_CALL(mock_io_ctx, aio_unwatch(m_watch_handle, _)) + .WillOnce(DoAll(Invoke([&mock_image_ctx, &mock_io_ctx, r](uint64_t handle, + librados::AioCompletionImpl *c) { + c->get(); + mock_image_ctx.image_ctx->op_work_queue->queue(new FunctionContext([&mock_io_ctx, c](int r) { + mock_io_ctx.get_mock_rados_client()->finish_aio_completion(c, r); + }), r); + }), + Return(0))); + } + + struct WatchCtx : public librados::WatchCtx2 { + void handle_notify(uint64_t, uint64_t, uint64_t, + ceph::bufferlist&) override { + ceph_abort(); + } + void handle_error(uint64_t, int) override { + ceph_abort(); + } + }; + + RWLock m_watch_lock; + WatchCtx m_watch_ctx; + uint64_t m_watch_handle = 123; +}; + +TEST_F(TestMockWatcherRewatchRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + InSequence seq; + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, 0); + + C_SaferCond ctx; + MockRewatchRequest *req = MockRewatchRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.header_oid, + m_watch_lock, + &m_watch_ctx, + &m_watch_handle, + &ctx); + { + RWLock::WLocker watch_locker(m_watch_lock); + req->send(); + } + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(234U, m_watch_handle); +} + +TEST_F(TestMockWatcherRewatchRequest, UnwatchError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + InSequence seq; + expect_aio_unwatch(mock_image_ctx, -EINVAL); + expect_aio_watch(mock_image_ctx, 0); + + C_SaferCond ctx; + MockRewatchRequest *req = MockRewatchRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.header_oid, + m_watch_lock, + &m_watch_ctx, + &m_watch_handle, + &ctx); + { + RWLock::WLocker watch_locker(m_watch_lock); + req->send(); + } + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(234U, m_watch_handle); +} + +TEST_F(TestMockWatcherRewatchRequest, WatchBlacklist) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + InSequence seq; + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, -EBLACKLISTED); + + C_SaferCond ctx; + MockRewatchRequest *req = MockRewatchRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.header_oid, + m_watch_lock, + &m_watch_ctx, + &m_watch_handle, + &ctx); + { + RWLock::WLocker watch_locker(m_watch_lock); + req->send(); + } + ASSERT_EQ(-EBLACKLISTED, ctx.wait()); + ASSERT_EQ(0U, m_watch_handle); +} + +TEST_F(TestMockWatcherRewatchRequest, WatchDNE) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + InSequence seq; + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, -ENOENT); + + C_SaferCond ctx; + MockRewatchRequest *req = MockRewatchRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.header_oid, + m_watch_lock, + &m_watch_ctx, + &m_watch_handle, + &ctx); + { + RWLock::WLocker watch_locker(m_watch_lock); + req->send(); + } + ASSERT_EQ(-ENOENT, ctx.wait()); + ASSERT_EQ(0U, m_watch_handle); +} + +TEST_F(TestMockWatcherRewatchRequest, WatchError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + InSequence seq; + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + MockRewatchRequest *req = MockRewatchRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.header_oid, + m_watch_lock, + &m_watch_ctx, + &m_watch_handle, + &ctx); + { + RWLock::WLocker watch_locker(m_watch_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, ctx.wait()); + ASSERT_EQ(0U, m_watch_handle); +} + +TEST_F(TestMockWatcherRewatchRequest, InvalidWatchHandler) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + + m_watch_handle = 0; + + C_SaferCond ctx; + MockRewatchRequest *req = MockRewatchRequest::create(mock_image_ctx.md_ctx, + mock_image_ctx.header_oid, + m_watch_lock, + &m_watch_ctx, + &m_watch_handle, + &ctx); + { + RWLock::WLocker watch_locker(m_watch_lock); + req->send(); + } + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(234U, m_watch_handle); +} + +} // namespace watcher +} // namespace librbd |