summaryrefslogtreecommitdiffstats
path: root/src/test/librbd
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/librbd')
-rw-r--r--src/test/librbd/CMakeLists.txt175
-rw-r--r--src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc583
-rw-r--r--src/test/librbd/deep_copy/test_mock_MetadataCopyRequest.cc180
-rw-r--r--src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc1040
-rw-r--r--src/test/librbd/deep_copy/test_mock_SetHeadRequest.cc293
-rw-r--r--src/test/librbd/deep_copy/test_mock_SnapshotCopyRequest.cc917
-rw-r--r--src/test/librbd/deep_copy/test_mock_SnapshotCreateRequest.cc256
-rw-r--r--src/test/librbd/exclusive_lock/test_mock_PostAcquireRequest.cc514
-rw-r--r--src/test/librbd/exclusive_lock/test_mock_PreAcquireRequest.cc92
-rw-r--r--src/test/librbd/exclusive_lock/test_mock_PreReleaseRequest.cc295
-rw-r--r--src/test/librbd/fsx.cc3448
-rw-r--r--src/test/librbd/image/test_mock_AttachChildRequest.cc272
-rw-r--r--src/test/librbd/image/test_mock_AttachParentRequest.cc155
-rw-r--r--src/test/librbd/image/test_mock_CloneRequest.cc934
-rw-r--r--src/test/librbd/image/test_mock_DetachChildRequest.cc356
-rw-r--r--src/test/librbd/image/test_mock_DetachParentRequest.cc135
-rw-r--r--src/test/librbd/image/test_mock_ListWatchersRequest.cc212
-rw-r--r--src/test/librbd/image/test_mock_PreRemoveRequest.cc446
-rw-r--r--src/test/librbd/image/test_mock_RefreshRequest.cc1383
-rw-r--r--src/test/librbd/image/test_mock_RemoveRequest.cc468
-rw-r--r--src/test/librbd/image/test_mock_ValidatePoolRequest.cc229
-rw-r--r--src/test/librbd/io/test_mock_CopyupRequest.cc1068
-rw-r--r--src/test/librbd/io/test_mock_ImageRequest.cc468
-rw-r--r--src/test/librbd/io/test_mock_ImageRequestWQ.cc465
-rw-r--r--src/test/librbd/io/test_mock_ObjectRequest.cc1394
-rw-r--r--src/test/librbd/journal/test_Entries.cc226
-rw-r--r--src/test/librbd/journal/test_Replay.cc872
-rw-r--r--src/test/librbd/journal/test_mock_OpenRequest.cc194
-rw-r--r--src/test/librbd/journal/test_mock_PromoteRequest.cc356
-rw-r--r--src/test/librbd/journal/test_mock_Replay.cc2080
-rw-r--r--src/test/librbd/journal/test_mock_ResetRequest.cc264
-rw-r--r--src/test/librbd/managed_lock/test_mock_AcquireRequest.cc270
-rw-r--r--src/test/librbd/managed_lock/test_mock_BreakRequest.cc471
-rw-r--r--src/test/librbd/managed_lock/test_mock_GetLockerRequest.cc265
-rw-r--r--src/test/librbd/managed_lock/test_mock_ReacquireRequest.cc123
-rw-r--r--src/test/librbd/managed_lock/test_mock_ReleaseRequest.cc91
-rw-r--r--src/test/librbd/mirror/test_mock_DisableRequest.cc585
-rw-r--r--src/test/librbd/mock/MockContextWQ.h19
-rw-r--r--src/test/librbd/mock/MockExclusiveLock.h41
-rw-r--r--src/test/librbd/mock/MockImageCtx.cc10
-rw-r--r--src/test/librbd/mock/MockImageCtx.h320
-rw-r--r--src/test/librbd/mock/MockImageState.h34
-rw-r--r--src/test/librbd/mock/MockImageWatcher.h29
-rw-r--r--src/test/librbd/mock/MockJournal.cc10
-rw-r--r--src/test/librbd/mock/MockJournal.h92
-rw-r--r--src/test/librbd/mock/MockJournalPolicy.h22
-rw-r--r--src/test/librbd/mock/MockObjectMap.h70
-rw-r--r--src/test/librbd/mock/MockOperations.h69
-rw-r--r--src/test/librbd/mock/MockReadahead.h21
-rw-r--r--src/test/librbd/mock/cache/MockImageCache.h56
-rw-r--r--src/test/librbd/mock/exclusive_lock/MockPolicy.h23
-rw-r--r--src/test/librbd/mock/io/MockImageRequestWQ.h25
-rw-r--r--src/test/librbd/mock/io/MockObjectDispatch.h121
-rw-r--r--src/test/librbd/mock/io/MockObjectDispatcher.h42
-rw-r--r--src/test/librbd/object_map/mock/MockInvalidateRequest.h41
-rw-r--r--src/test/librbd/object_map/test_mock_InvalidateRequest.cc153
-rw-r--r--src/test/librbd/object_map/test_mock_LockRequest.cc220
-rw-r--r--src/test/librbd/object_map/test_mock_RefreshRequest.cc453
-rw-r--r--src/test/librbd/object_map/test_mock_ResizeRequest.cc146
-rw-r--r--src/test/librbd/object_map/test_mock_SnapshotCreateRequest.cc224
-rw-r--r--src/test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc330
-rw-r--r--src/test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc147
-rw-r--r--src/test/librbd/object_map/test_mock_UnlockRequest.cc69
-rw-r--r--src/test/librbd/object_map/test_mock_UpdateRequest.cc280
-rw-r--r--src/test/librbd/operation/test_mock_DisableFeaturesRequest.cc526
-rw-r--r--src/test/librbd/operation/test_mock_EnableFeaturesRequest.cc627
-rw-r--r--src/test/librbd/operation/test_mock_Request.cc175
-rw-r--r--src/test/librbd/operation/test_mock_ResizeRequest.cc461
-rw-r--r--src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc313
-rw-r--r--src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc192
-rw-r--r--src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc833
-rw-r--r--src/test/librbd/operation/test_mock_SnapshotRollbackRequest.cc367
-rw-r--r--src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc277
-rw-r--r--src/test/librbd/operation/test_mock_TrimRequest.cc474
-rw-r--r--src/test/librbd/rbdrw.py30
-rw-r--r--src/test/librbd/test_BlockGuard.cc98
-rw-r--r--src/test/librbd/test_DeepCopy.cc757
-rw-r--r--src/test/librbd/test_Groups.cc321
-rw-r--r--src/test/librbd/test_ImageWatcher.cc697
-rw-r--r--src/test/librbd/test_Migration.cc1350
-rw-r--r--src/test/librbd/test_MirroringWatcher.cc101
-rw-r--r--src/test/librbd/test_ObjectMap.cc236
-rw-r--r--src/test/librbd/test_Operations.cc26
-rw-r--r--src/test/librbd/test_Trash.cc108
-rw-r--r--src/test/librbd/test_fixture.cc145
-rw-r--r--src/test/librbd/test_fixture.h58
-rw-r--r--src/test/librbd/test_internal.cc1754
-rw-r--r--src/test/librbd/test_librbd.cc8056
-rw-r--r--src/test/librbd/test_main.cc72
-rw-r--r--src/test/librbd/test_mirroring.cc953
-rw-r--r--src/test/librbd/test_mock_ConfigWatcher.cc100
-rw-r--r--src/test/librbd/test_mock_DeepCopyRequest.cc457
-rw-r--r--src/test/librbd/test_mock_ExclusiveLock.cc723
-rw-r--r--src/test/librbd/test_mock_Journal.cc1531
-rw-r--r--src/test/librbd/test_mock_ManagedLock.cc691
-rw-r--r--src/test/librbd/test_mock_ObjectMap.cc278
-rw-r--r--src/test/librbd/test_mock_TrashWatcher.cc95
-rw-r--r--src/test/librbd/test_mock_Watcher.cc404
-rw-r--r--src/test/librbd/test_mock_fixture.cc123
-rw-r--r--src/test/librbd/test_mock_fixture.h88
-rwxr-xr-xsrc/test/librbd/test_notify.py180
-rw-r--r--src/test/librbd/test_support.cc128
-rw-r--r--src/test/librbd/test_support.h38
-rw-r--r--src/test/librbd/trash/test_mock_MoveRequest.cc230
-rw-r--r--src/test/librbd/trash/test_mock_RemoveRequest.cc231
-rw-r--r--src/test/librbd/watcher/test_mock_RewatchRequest.cc229
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,
+ &copy_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> &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,
+ 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, &registered_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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, &current_tag, &current_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 &timestamp_lock;
+ RWLock &parent_lock;
+ RWLock &object_map_lock;
+ Mutex &async_ops_lock;
+ Mutex &copyup_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> &current_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> &current_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> &current_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", &notify_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", &notify_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", &notify_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", &notify_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",
+ &notify_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",
+ &notify_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",
+ &notify_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", &notify_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, &timestamp));
+ 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(&timestamp));
+ 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, &timestamp));
+ 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, &timestamp));
+ 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, &timestamp));
+ 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> &current_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> &current_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> &current_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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