summaryrefslogtreecommitdiffstats
path: root/src/test/librbd
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/test/librbd/CMakeLists.txt228
-rw-r--r--src/test/librbd/cache/pwl/test_WriteLogMap.cc336
-rw-r--r--src/test/librbd/cache/pwl/test_mock_ReplicatedWriteLog.cc743
-rw-r--r--src/test/librbd/cache/pwl/test_mock_SSDWriteLog.cc761
-rw-r--r--src/test/librbd/cache/test_mock_ParentCacheObjectDispatch.cc427
-rw-r--r--src/test/librbd/cache/test_mock_WriteAroundObjectDispatch.cc703
-rw-r--r--src/test/librbd/crypto/luks/test_mock_FormatRequest.cc195
-rw-r--r--src/test/librbd/crypto/luks/test_mock_LoadRequest.cc221
-rw-r--r--src/test/librbd/crypto/openssl/test_DataCryptor.cc118
-rw-r--r--src/test/librbd/crypto/test_mock_BlockCrypto.cc156
-rw-r--r--src/test/librbd/crypto/test_mock_CryptoContextPool.cc54
-rw-r--r--src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc798
-rw-r--r--src/test/librbd/crypto/test_mock_FormatRequest.cc195
-rw-r--r--src/test/librbd/crypto/test_mock_LoadRequest.cc125
-rw-r--r--src/test/librbd/crypto/test_mock_ShutDownCryptoRequest.cc144
-rw-r--r--src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc805
-rw-r--r--src/test/librbd/deep_copy/test_mock_MetadataCopyRequest.cc220
-rw-r--r--src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc1103
-rw-r--r--src/test/librbd/deep_copy/test_mock_SetHeadRequest.cc296
-rw-r--r--src/test/librbd/deep_copy/test_mock_SnapshotCopyRequest.cc923
-rw-r--r--src/test/librbd/deep_copy/test_mock_SnapshotCreateRequest.cc262
-rw-r--r--src/test/librbd/exclusive_lock/test_mock_PostAcquireRequest.cc582
-rw-r--r--src/test/librbd/exclusive_lock/test_mock_PreAcquireRequest.cc92
-rw-r--r--src/test/librbd/exclusive_lock/test_mock_PreReleaseRequest.cc388
-rw-r--r--src/test/librbd/fsx.cc3448
-rw-r--r--src/test/librbd/image/test_mock_AttachChildRequest.cc275
-rw-r--r--src/test/librbd/image/test_mock_AttachParentRequest.cc155
-rw-r--r--src/test/librbd/image/test_mock_CloneRequest.cc960
-rw-r--r--src/test/librbd/image/test_mock_DetachChildRequest.cc454
-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.cc465
-rw-r--r--src/test/librbd/image/test_mock_RefreshRequest.cc1757
-rw-r--r--src/test/librbd/image/test_mock_RemoveRequest.cc480
-rw-r--r--src/test/librbd/image/test_mock_ValidatePoolRequest.cc223
-rw-r--r--src/test/librbd/io/test_mock_CopyupRequest.cc1334
-rw-r--r--src/test/librbd/io/test_mock_ImageRequest.cc562
-rw-r--r--src/test/librbd/io/test_mock_ObjectRequest.cc1961
-rw-r--r--src/test/librbd/io/test_mock_QosImageDispatch.cc89
-rw-r--r--src/test/librbd/io/test_mock_SimpleSchedulerObjectDispatch.cc823
-rw-r--r--src/test/librbd/journal/test_Entries.cc227
-rw-r--r--src/test/librbd/journal/test_Replay.cc886
-rw-r--r--src/test/librbd/journal/test_mock_OpenRequest.cc193
-rw-r--r--src/test/librbd/journal/test_mock_PromoteRequest.cc356
-rw-r--r--src/test/librbd/journal/test_mock_Replay.cc2042
-rw-r--r--src/test/librbd/journal/test_mock_ResetRequest.cc278
-rw-r--r--src/test/librbd/managed_lock/test_mock_AcquireRequest.cc271
-rw-r--r--src/test/librbd/managed_lock/test_mock_BreakRequest.cc484
-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/migration/test_mock_FileStream.cc217
-rw-r--r--src/test/librbd/migration/test_mock_HttpClient.cc890
-rw-r--r--src/test/librbd/migration/test_mock_HttpStream.cc194
-rw-r--r--src/test/librbd/migration/test_mock_QCOWFormat.cc1259
-rw-r--r--src/test/librbd/migration/test_mock_RawFormat.cc523
-rw-r--r--src/test/librbd/migration/test_mock_RawSnapshot.cc255
-rw-r--r--src/test/librbd/migration/test_mock_S3Stream.cc238
-rw-r--r--src/test/librbd/migration/test_mock_Utils.cc47
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc388
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc404
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_ImageMeta.cc159
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc389
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_UnlinkPeerRequest.cc385
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_Utils.cc177
-rw-r--r--src/test/librbd/mirror/test_mock_DisableRequest.cc694
-rw-r--r--src/test/librbd/mock/MockContextWQ.h19
-rw-r--r--src/test/librbd/mock/MockExclusiveLock.h50
-rw-r--r--src/test/librbd/mock/MockImageCtx.cc56
-rw-r--r--src/test/librbd/mock/MockImageCtx.h343
-rw-r--r--src/test/librbd/mock/MockImageState.h39
-rw-r--r--src/test/librbd/mock/MockImageWatcher.h34
-rw-r--r--src/test/librbd/mock/MockJournal.cc10
-rw-r--r--src/test/librbd/mock/MockJournal.h96
-rw-r--r--src/test/librbd/mock/MockJournalPolicy.h22
-rw-r--r--src/test/librbd/mock/MockObjectMap.h73
-rw-r--r--src/test/librbd/mock/MockOperations.h72
-rw-r--r--src/test/librbd/mock/MockPluginRegistry.h21
-rw-r--r--src/test/librbd/mock/MockReadahead.h21
-rw-r--r--src/test/librbd/mock/MockSafeTimer.h20
-rw-r--r--src/test/librbd/mock/cache/MockImageCache.h58
-rw-r--r--src/test/librbd/mock/crypto/MockCryptoInterface.h33
-rw-r--r--src/test/librbd/mock/crypto/MockDataCryptor.h43
-rw-r--r--src/test/librbd/mock/crypto/MockEncryptionFormat.h41
-rw-r--r--src/test/librbd/mock/exclusive_lock/MockPolicy.h23
-rw-r--r--src/test/librbd/mock/io/MockImageDispatch.h99
-rw-r--r--src/test/librbd/mock/io/MockImageDispatcher.h48
-rw-r--r--src/test/librbd/mock/io/MockObjectDispatch.h137
-rw-r--r--src/test/librbd/mock/io/MockObjectDispatcher.h44
-rw-r--r--src/test/librbd/mock/io/MockQosImageDispatch.h24
-rw-r--r--src/test/librbd/mock/migration/MockSnapshotInterface.h44
-rw-r--r--src/test/librbd/mock/migration/MockStreamInterface.h29
-rw-r--r--src/test/librbd/object_map/mock/MockInvalidateRequest.h41
-rw-r--r--src/test/librbd/object_map/test_mock_DiffRequest.cc493
-rw-r--r--src/test/librbd/object_map/test_mock_InvalidateRequest.cc158
-rw-r--r--src/test/librbd/object_map/test_mock_LockRequest.cc221
-rw-r--r--src/test/librbd/object_map/test_mock_RefreshRequest.cc465
-rw-r--r--src/test/librbd/object_map/test_mock_ResizeRequest.cc154
-rw-r--r--src/test/librbd/object_map/test_mock_SnapshotCreateRequest.cc232
-rw-r--r--src/test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc345
-rw-r--r--src/test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc148
-rw-r--r--src/test/librbd/object_map/test_mock_UnlockRequest.cc69
-rw-r--r--src/test/librbd/object_map/test_mock_UpdateRequest.cc291
-rw-r--r--src/test/librbd/operation/test_mock_DisableFeaturesRequest.cc538
-rw-r--r--src/test/librbd/operation/test_mock_EnableFeaturesRequest.cc644
-rw-r--r--src/test/librbd/operation/test_mock_Request.cc175
-rw-r--r--src/test/librbd/operation/test_mock_ResizeRequest.cc435
-rw-r--r--src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc495
-rw-r--r--src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc193
-rw-r--r--src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc924
-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.cc759
-rw-r--r--src/test/librbd/test_Groups.cc423
-rw-r--r--src/test/librbd/test_ImageWatcher.cc885
-rw-r--r--src/test/librbd/test_Migration.cc1359
-rw-r--r--src/test/librbd/test_MirroringWatcher.cc101
-rw-r--r--src/test/librbd/test_ObjectMap.cc238
-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.cc165
-rw-r--r--src/test/librbd/test_fixture.h60
-rw-r--r--src/test/librbd/test_internal.cc1789
-rw-r--r--src/test/librbd/test_librbd.cc9475
-rw-r--r--src/test/librbd/test_main.cc71
-rw-r--r--src/test/librbd/test_mirroring.cc1541
-rw-r--r--src/test/librbd/test_mock_ConfigWatcher.cc100
-rw-r--r--src/test/librbd/test_mock_DeepCopyRequest.cc466
-rw-r--r--src/test/librbd/test_mock_ExclusiveLock.cc831
-rw-r--r--src/test/librbd/test_mock_Journal.cc1574
-rw-r--r--src/test/librbd/test_mock_ManagedLock.cc694
-rw-r--r--src/test/librbd/test_mock_ObjectMap.cc285
-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.cc134
-rw-r--r--src/test/librbd/test_mock_fixture.h89
-rwxr-xr-xsrc/test/librbd/test_notify.py183
-rw-r--r--src/test/librbd/test_support.cc137
-rw-r--r--src/test/librbd/test_support.h40
-rw-r--r--src/test/librbd/trash/test_mock_MoveRequest.cc230
-rw-r--r--src/test/librbd/trash/test_mock_RemoveRequest.cc230
-rw-r--r--src/test/librbd/watcher/test_mock_RewatchRequest.cc227
145 files changed, 66128 insertions, 0 deletions
diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt
new file mode 100644
index 000000000..6d8371fd2
--- /dev/null
+++ b/src/test/librbd/CMakeLists.txt
@@ -0,0 +1,228 @@
+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
+ cache/test_mock_WriteAroundObjectDispatch.cc
+ cache/test_mock_ParentCacheObjectDispatch.cc
+ crypto/test_mock_BlockCrypto.cc
+ crypto/test_mock_CryptoContextPool.cc
+ crypto/test_mock_CryptoObjectDispatch.cc
+ crypto/test_mock_FormatRequest.cc
+ crypto/test_mock_LoadRequest.cc
+ crypto/test_mock_ShutDownCryptoRequest.cc
+ crypto/openssl/test_DataCryptor.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_ObjectRequest.cc
+ io/test_mock_SimpleSchedulerObjectDispatch.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
+ migration/test_mock_FileStream.cc
+ migration/test_mock_HttpClient.cc
+ migration/test_mock_HttpStream.cc
+ migration/test_mock_RawFormat.cc
+ migration/test_mock_RawSnapshot.cc
+ migration/test_mock_QCOWFormat.cc
+ migration/test_mock_S3Stream.cc
+ migration/test_mock_Utils.cc
+ mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc
+ mirror/snapshot/test_mock_CreatePrimaryRequest.cc
+ mirror/snapshot/test_mock_ImageMeta.cc
+ mirror/snapshot/test_mock_PromoteRequest.cc
+ mirror/snapshot/test_mock_UnlinkPeerRequest.cc
+ mirror/snapshot/test_mock_Utils.cc
+ mirror/test_mock_DisableRequest.cc
+ object_map/test_mock_DiffRequest.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
+ )
+
+if(WITH_RBD_RWL OR WITH_RBD_SSD_CACHE)
+ list(APPEND unittest_librbd_srcs
+ cache/pwl/test_WriteLogMap.cc)
+ if(WITH_RBD_RWL)
+ list(APPEND unittest_librbd_srcs
+ cache/pwl/test_mock_ReplicatedWriteLog.cc)
+ endif()
+ if(WITH_RBD_SSD_CACHE)
+ list(APPEND unittest_librbd_srcs
+ cache/pwl/test_mock_SSDWriteLog.cc)
+ endif()
+endif()
+
+if(LINUX AND HAVE_LIBCRYPTSETUP)
+ list(APPEND unittest_librbd_srcs
+ crypto/luks/test_mock_FormatRequest.cc
+ crypto/luks/test_mock_LoadRequest.cc)
+endif()
+
+add_executable(unittest_librbd
+ ${unittest_librbd_srcs}
+ $<TARGET_OBJECTS:common_texttable_obj>)
+target_compile_definitions(unittest_librbd PRIVATE "TEST_LIBRBD_INTERNALS")
+add_dependencies(unittest_librbd
+ cls_journal
+ cls_lock
+ cls_rbd)
+target_link_libraries(unittest_librbd
+ rbd_test
+ rbd_api
+ rbd_internal
+ rbd_test_mock
+ journal
+ journal_test_mock
+ cls_rbd_client
+ cls_lock_client
+ cls_journal_client
+ rbd_types
+ rados_test_stub
+ librados
+ ceph_immutable_object_cache_lib
+ osdc
+ ceph-common
+ global
+ OpenSSL::SSL
+ ${UNITTEST_LIBS})
+
+if(WITH_RBD_RWL OR WITH_RBD_SSD_CACHE)
+ target_link_libraries(unittest_librbd
+ librbd_plugin_pwl_cache)
+endif()
+
+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
+ libneorados
+ 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/cache/pwl/test_WriteLogMap.cc b/src/test/librbd/cache/pwl/test_WriteLogMap.cc
new file mode 100644
index 000000000..7263d0831
--- /dev/null
+++ b/src/test/librbd/cache/pwl/test_WriteLogMap.cc
@@ -0,0 +1,336 @@
+// -*- 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/cache/pwl/LogMap.cc"
+
+void register_test_write_log_map() {
+}
+
+namespace librbd {
+namespace cache {
+namespace pwl {
+
+struct TestLogEntry {
+ uint64_t image_offset_bytes;
+ uint64_t write_bytes;
+ uint32_t referring_map_entries = 0;
+ TestLogEntry(const uint64_t image_offset_bytes, const uint64_t write_bytes)
+ : image_offset_bytes(image_offset_bytes), write_bytes(write_bytes) {
+ }
+ uint64_t get_offset_bytes() {
+ return image_offset_bytes;
+ }
+ uint64_t get_write_bytes() {
+ return write_bytes;
+ }
+ BlockExtent block_extent() {
+ return BlockExtent(image_offset_bytes, image_offset_bytes + write_bytes);
+ }
+ uint32_t get_map_ref() {
+ return referring_map_entries;
+ }
+ void inc_map_ref() {
+ referring_map_entries++;
+ }
+ void dec_map_ref() {
+ referring_map_entries--;
+ }
+ friend std::ostream &operator<<(std::ostream &os,
+ const TestLogEntry &entry) {
+ os << "referring_map_entries=" << entry.referring_map_entries << ", "
+ << "image_offset_bytes=" << entry.image_offset_bytes << ", "
+ << "write_bytes=" << entry.write_bytes;
+ return os;
+ };
+};
+
+typedef std::list<std::shared_ptr<TestLogEntry>> TestLogEntries;
+typedef LogMapEntry<TestLogEntry> TestMapEntry;
+typedef LogMapEntries<TestLogEntry> TestLogMapEntries;
+typedef LogMap<TestLogEntry> TestLogMap;
+
+class TestWriteLogMap : public TestFixture {
+public:
+ void SetUp() override {
+ TestFixture::SetUp();
+ m_cct = reinterpret_cast<CephContext*>(m_ioctx.cct());
+ }
+
+ CephContext *m_cct;
+};
+
+TEST_F(TestWriteLogMap, Simple) {
+ TestLogEntries es;
+ TestLogMapEntries lme;
+ TestLogMap map(m_cct);
+
+ /* LogEntry takes offset, length, in bytes */
+ auto e1 = make_shared<TestLogEntry>(4, 8);
+ TestLogEntry *e1_ptr = e1.get();
+ ASSERT_EQ(4, e1_ptr->get_offset_bytes());
+ ASSERT_EQ(8, e1_ptr->get_write_bytes());
+ map.add_log_entry(e1);
+
+ /* BlockExtent takes first, last, in blocks */
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(0, 100));
+ int numfound = found0.size();
+ /* Written range includes the single write above */
+ ASSERT_EQ(1, numfound);
+ ASSERT_EQ(e1, found0.front().log_entry);
+
+ /* Nothing before that */
+ found0 = map.find_map_entries(BlockExtent(0, 3));
+ numfound = found0.size();
+ ASSERT_EQ(0, numfound);
+
+ /* Nothing after that */
+ found0 = map.find_map_entries(BlockExtent(12, 99));
+ numfound = found0.size();
+ ASSERT_EQ(0, numfound);
+
+ /* 4-11 will be e1 */
+ for (int i=4; i<12; i++) {
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
+ int numfound = found0.size();
+ ASSERT_EQ(1, numfound);
+ ASSERT_EQ(e1, found0.front().log_entry);
+ }
+
+ map.remove_log_entry(e1);
+ /* Nothing should be found */
+ for (int i=4; i<12; i++) {
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
+ int numfound = found0.size();
+ ASSERT_EQ(0, numfound);
+ }
+}
+
+TEST_F(TestWriteLogMap, OverlapFront) {
+ TestLogMap map(m_cct);
+
+ auto e0 = make_shared<TestLogEntry>(4, 8);
+ map.add_log_entry(e0);
+ /* replaces block 4-7 of e0 */
+ auto e1 = make_shared<TestLogEntry>(0, 8);
+ map.add_log_entry(e1);
+
+ /* Written range includes the two writes above */
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(0, 100));
+ int numfound = found0.size();
+ ASSERT_EQ(2, numfound);
+ ASSERT_EQ(e1, found0.front().log_entry);
+ ASSERT_EQ(0, found0.front().block_extent.block_start);
+ ASSERT_EQ(8, found0.front().block_extent.block_end);
+ found0.pop_front();
+ ASSERT_EQ(e0, found0.front().log_entry);
+ ASSERT_EQ(8, found0.front().block_extent.block_start);
+ ASSERT_EQ(12, found0.front().block_extent.block_end);
+
+ /* 0-7 will be e1 */
+ for (int i=0; i<8; i++) {
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
+ int numfound = found0.size();
+ ASSERT_EQ(1, numfound);
+ ASSERT_EQ(e1, found0.front().log_entry);
+ }
+
+ /* 8-11 will be e0 */
+ for (int i=8; i<12; i++) {
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
+ int numfound = found0.size();
+ ASSERT_EQ(1, numfound);
+ ASSERT_EQ(e0, found0.front().log_entry);
+ }
+}
+
+TEST_F(TestWriteLogMap, OverlapBack) {
+ TestLogMap map(m_cct);
+
+ auto e0 = make_shared<TestLogEntry>(0, 8);
+ map.add_log_entry(e0);
+ /* replaces block 4-7 of e0 */
+ auto e1 = make_shared<TestLogEntry>(4, 8);
+ map.add_log_entry(e1);
+
+ /* Written range includes the two writes above */
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(0, 100));
+ int numfound = found0.size();
+ ASSERT_EQ(2, numfound);
+ ASSERT_EQ(e0, found0.front().log_entry);
+ ASSERT_EQ(0, found0.front().block_extent.block_start);
+ ASSERT_EQ(4, found0.front().block_extent.block_end);
+ found0.pop_front();
+ ASSERT_EQ(e1, found0.front().log_entry);
+ ASSERT_EQ(4, found0.front().block_extent.block_start);
+ ASSERT_EQ(12, found0.front().block_extent.block_end);
+
+ /* 0-3 will be e0 */
+ for (int i=0; i<4; i++) {
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
+ int numfound = found0.size();
+ ASSERT_EQ(1, numfound);
+ ASSERT_EQ(e0, found0.front().log_entry);
+ }
+
+ /* 4-11 will be e1 */
+ for (int i=4; i<12; i++) {
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
+ int numfound = found0.size();
+ ASSERT_EQ(1, numfound);
+ ASSERT_EQ(e1, found0.front().log_entry);
+ }
+
+ map.remove_log_entry(e0);
+
+ /* 0-3 will find nothing */
+ for (int i=0; i<4; i++) {
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
+ int numfound = found0.size();
+ ASSERT_EQ(0, numfound);
+ }
+
+ /* 4-11 will still be e1 */
+ for (int i=4; i<12; i++) {
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(i, i + 1));
+ int numfound = found0.size();
+ ASSERT_EQ(1, numfound);
+ ASSERT_EQ(e1, found0.front().log_entry);
+ }
+
+}
+
+TEST_F(TestWriteLogMap, OverlapMiddle) {
+ TestLogMap map(m_cct);
+
+ auto e0 = make_shared<TestLogEntry>(0, 1);
+ map.add_log_entry(e0);
+
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(0, 1));
+ int numfound = found0.size();
+ ASSERT_EQ(1, numfound);
+ ASSERT_EQ(e0, found0.front().log_entry);
+ TestLogEntries entries = map.find_log_entries(BlockExtent(0, 1));
+ int entriesfound = entries.size();
+ ASSERT_EQ(1, entriesfound);
+ ASSERT_EQ(e0, entries.front());
+
+ auto e1 = make_shared<TestLogEntry>(1, 1);
+ map.add_log_entry(e1);
+
+ found0 = map.find_map_entries(BlockExtent(1, 2));
+ numfound = found0.size();
+ ASSERT_EQ(1, numfound);
+ ASSERT_EQ(e1, found0.front().log_entry);
+ entries = map.find_log_entries(BlockExtent(1, 2));
+ entriesfound = entries.size();
+ ASSERT_EQ(1, entriesfound);
+ ASSERT_EQ(e1, entries.front());
+
+ auto e2 = make_shared<TestLogEntry>(2, 1);
+ map.add_log_entry(e2);
+
+ found0 = map.find_map_entries(BlockExtent(2, 3));
+ numfound = found0.size();
+ ASSERT_EQ(1, numfound);
+ ASSERT_EQ(e2, found0.front().log_entry);
+ entries = map.find_log_entries(BlockExtent(2, 3));
+ entriesfound = entries.size();
+ ASSERT_EQ(1, entriesfound);
+ ASSERT_EQ(e2, entries.front());
+
+ /* replaces e1 */
+ auto e3 = make_shared<TestLogEntry>(1, 1);
+ map.add_log_entry(e3);
+
+ found0 = map.find_map_entries(BlockExtent(1, 2));
+ numfound = found0.size();
+ ASSERT_EQ(1, numfound);
+ ASSERT_EQ(e3, found0.front().log_entry);
+ entries = map.find_log_entries(BlockExtent(1, 2));
+ entriesfound = entries.size();
+ ASSERT_EQ(1, entriesfound);
+ ASSERT_EQ(e3, entries.front());
+
+ found0 = map.find_map_entries(BlockExtent(0, 100));
+ numfound = found0.size();
+ ASSERT_EQ(3, numfound);
+ ASSERT_EQ(e0, found0.front().log_entry);
+ found0.pop_front();
+ ASSERT_EQ(e3, found0.front().log_entry);
+ found0.pop_front();
+ ASSERT_EQ(e2, found0.front().log_entry);
+ entries = map.find_log_entries(BlockExtent(0, 100));
+ entriesfound = entries.size();
+ ASSERT_EQ(3, entriesfound);
+ ASSERT_EQ(e0, entries.front());
+ entries.pop_front();
+ ASSERT_EQ(e3, entries.front());
+ entries.pop_front();
+ ASSERT_EQ(e2, entries.front());
+
+ entries.clear();
+ entries.emplace_back(e0);
+ entries.emplace_back(e1);
+ map.remove_log_entries(entries);
+
+ found0 = map.find_map_entries(BlockExtent(0, 100));
+ numfound = found0.size();
+ ASSERT_EQ(2, numfound);
+ ASSERT_EQ(e3, found0.front().log_entry);
+ found0.pop_front();
+ ASSERT_EQ(e2, found0.front().log_entry);
+}
+
+TEST_F(TestWriteLogMap, OverlapSplit) {
+ TestLogMap map(m_cct);
+
+ auto e0 = make_shared<TestLogEntry>(0, 8);
+ map.add_log_entry(e0);
+
+ /* Splits e0 at 1 */
+ auto e1 = make_shared<TestLogEntry>(1, 1);
+ map.add_log_entry(e1);
+
+ /* Splits e0 again at 4 */
+ auto e2 = make_shared<TestLogEntry>(4, 2);
+ map.add_log_entry(e2);
+
+ /* Replaces one block of e2, and one of e0 */
+ auto e3 = make_shared<TestLogEntry>(5, 2);
+ map.add_log_entry(e3);
+
+ /* Expecting: 0:e0, 1:e1, 2..3:e0, 4:e2, 5..6:e3, 7:e0 */
+ TestLogMapEntries found0 = map.find_map_entries(BlockExtent(0, 100));
+ int numfound = found0.size();
+ ASSERT_EQ(6, numfound);
+ ASSERT_EQ(e0, found0.front().log_entry);
+ ASSERT_EQ(0, found0.front().block_extent.block_start);
+ ASSERT_EQ(1, found0.front().block_extent.block_end);
+ found0.pop_front();
+ ASSERT_EQ(e1, found0.front().log_entry);
+ ASSERT_EQ(1, found0.front().block_extent.block_start);
+ ASSERT_EQ(2, found0.front().block_extent.block_end);
+ found0.pop_front();
+ ASSERT_EQ(e0, found0.front().log_entry);
+ ASSERT_EQ(2, found0.front().block_extent.block_start);
+ ASSERT_EQ(4, found0.front().block_extent.block_end);
+ found0.pop_front();
+ ASSERT_EQ(e2, found0.front().log_entry);
+ ASSERT_EQ(4, found0.front().block_extent.block_start);
+ ASSERT_EQ(5, found0.front().block_extent.block_end);
+ found0.pop_front();
+ ASSERT_EQ(e3, found0.front().log_entry);
+ ASSERT_EQ(5, found0.front().block_extent.block_start);
+ ASSERT_EQ(7, found0.front().block_extent.block_end);
+ found0.pop_front();
+ ASSERT_EQ(e0, found0.front().log_entry);
+ ASSERT_EQ(7, found0.front().block_extent.block_start);
+ ASSERT_EQ(8, found0.front().block_extent.block_end);
+}
+
+} // namespace pwl
+} // namespace cache
+} // namespace librbd
diff --git a/src/test/librbd/cache/pwl/test_mock_ReplicatedWriteLog.cc b/src/test/librbd/cache/pwl/test_mock_ReplicatedWriteLog.cc
new file mode 100644
index 000000000..a37f58038
--- /dev/null
+++ b/src/test/librbd/cache/pwl/test_mock_ReplicatedWriteLog.cc
@@ -0,0 +1,743 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <iostream>
+#include "common/hostname.h"
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "include/rbd/librbd.hpp"
+#include "librbd/cache/pwl/ImageCacheState.h"
+#include "librbd/cache/pwl/Types.h"
+#include "librbd/cache/ImageWriteback.h"
+#include "librbd/plugin/Api.h"
+
+namespace librbd {
+namespace {
+
+struct MockContextRWL : public C_SaferCond {
+ MOCK_METHOD1(complete, void(int));
+ MOCK_METHOD1(finish, void(int));
+
+ void do_complete(int r) {
+ C_SaferCond::complete(r);
+ }
+};
+
+} // anonymous namespace
+
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+#include "librbd/cache/pwl/AbstractWriteLog.cc"
+#include "librbd/cache/pwl/rwl/WriteLog.cc"
+template class librbd::cache::pwl::rwl::WriteLog<librbd::MockImageCtx>;
+
+// template definitions
+#include "librbd/cache/ImageWriteback.cc"
+#include "librbd/cache/pwl/ImageCacheState.cc"
+#include "librbd/cache/pwl/Request.cc"
+#include "librbd/cache/pwl/rwl/Request.cc"
+#include "librbd/plugin/Api.cc"
+
+namespace librbd {
+namespace cache {
+namespace pwl {
+
+using ::testing::_;
+using ::testing::DoDefault;
+using ::testing::InSequence;
+using ::testing::Invoke;
+
+typedef io::Extent Extent;
+typedef io::Extents Extents;
+
+struct TestMockCacheReplicatedWriteLog : public TestMockFixture {
+ typedef librbd::cache::pwl::rwl::WriteLog<librbd::MockImageCtx> MockReplicatedWriteLog;
+ typedef librbd::cache::pwl::ImageCacheState<librbd::MockImageCtx> MockImageCacheStateRWL;
+ typedef librbd::cache::ImageWriteback<librbd::MockImageCtx> MockImageWriteback;
+ typedef librbd::plugin::Api<librbd::MockImageCtx> MockApi;
+
+ MockImageCacheStateRWL *get_cache_state(
+ MockImageCtx& mock_image_ctx, MockApi& mock_api) {
+ MockImageCacheStateRWL *rwl_state = new MockImageCacheStateRWL(&mock_image_ctx, mock_api);
+ return rwl_state;
+ }
+
+ void validate_cache_state(librbd::ImageCtx *image_ctx,
+ MockImageCacheStateRWL &state,
+ bool present, bool empty, bool clean,
+ string host, string path,
+ uint64_t size) {
+ ASSERT_EQ(present, state.present);
+ ASSERT_EQ(empty, state.empty);
+ ASSERT_EQ(clean, state.clean);
+
+ ASSERT_EQ(host, state.host);
+ ASSERT_EQ(path, state.path);
+ ASSERT_EQ(size, state.size);
+ }
+
+ void expect_context_complete(MockContextRWL& mock_context, int r) {
+ EXPECT_CALL(mock_context, complete(r))
+ .WillRepeatedly(Invoke([&mock_context](int r) {
+ mock_context.do_complete(r);
+ }));
+ }
+
+ void expect_metadata_set(MockImageCtx& mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.operations, execute_metadata_set(_, _, _))
+ .WillRepeatedly(Invoke([](std::string key, std::string val, Context* ctx) {
+ ctx->complete(0);
+ }));
+ }
+
+ void expect_metadata_remove(MockImageCtx& mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.operations, execute_metadata_remove(_, _))
+ .WillRepeatedly(Invoke([](std::string key, Context* ctx) {
+ ctx->complete(0);
+ }));
+ }
+};
+
+TEST_F(TestMockCacheReplicatedWriteLog, init_state_write) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockApi mock_api;
+ MockImageCacheStateRWL image_cache_state(&mock_image_ctx, mock_api);
+
+ validate_cache_state(ictx, image_cache_state, false, true, true, "", "", 0);
+
+ image_cache_state.empty = false;
+ image_cache_state.clean = false;
+ ceph::mutex lock = ceph::make_mutex("MockImageCacheStateRWL lock");
+ MockContextRWL finish_ctx;
+ expect_metadata_set(mock_image_ctx);
+ expect_context_complete(finish_ctx, 0);
+ std::unique_lock locker(lock);
+ image_cache_state.write_image_cache_state(locker, &finish_ctx);
+ ASSERT_FALSE(locker.owns_lock());
+ ASSERT_EQ(0, finish_ctx.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, init_state_json_write) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockApi mock_api;
+ MockImageCacheStateRWL image_cache_state(&mock_image_ctx, mock_api);
+
+ string strf = "{ \"present\": true, \"empty\": false, \"clean\": false, \
+ \"host\": \"testhost\", \
+ \"path\": \"/tmp\", \
+ \"mode\": \"rwl\", \
+ \"size\": 1024 }";
+ json_spirit::mValue json_root;
+ ASSERT_TRUE(json_spirit::read(strf.c_str(), json_root));
+ ASSERT_TRUE(image_cache_state.init_from_metadata(json_root));
+ validate_cache_state(ictx, image_cache_state, true, false, false,
+ "testhost", "/tmp", 1024);
+
+ MockContextRWL finish_ctx;
+ expect_metadata_remove(mock_image_ctx);
+ expect_context_complete(finish_ctx, 0);
+ image_cache_state.clear_image_cache_state(&finish_ctx);
+ ASSERT_EQ(0, finish_ctx.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, init_shutdown) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ MockContextRWL finish_ctx1;
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ rwl.shut_down(&finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, write) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+
+ MockContextRWL finish_ctx1;
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, flush) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ bufferlist bl_copy = bl;
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx_flush;
+ expect_context_complete(finish_ctx_flush, 0);
+ rwl.flush(&finish_ctx_flush);
+ ASSERT_EQ(0, finish_ctx_flush.wait());
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, flush_source_shutdown) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx_flush;
+ expect_context_complete(finish_ctx_flush, 0);
+ rwl.flush(io::FLUSH_SOURCE_SHUTDOWN, &finish_ctx_flush);
+ ASSERT_EQ(0, finish_ctx_flush.wait());
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, flush_source_internal) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx_flush;
+ expect_context_complete(finish_ctx_flush, 0);
+ rwl.flush(io::FLUSH_SOURCE_INTERNAL, &finish_ctx_flush);
+ ASSERT_EQ(0, finish_ctx_flush.wait());
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, flush_source_user) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ usleep(10000);
+ MockContextRWL finish_ctx_flush;
+ expect_context_complete(finish_ctx_flush, 0);
+ rwl.flush(io::FLUSH_SOURCE_USER, &finish_ctx_flush);
+ ASSERT_EQ(0, finish_ctx_flush.wait());
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, read_hit_rwl_cache) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ bufferlist bl_copy = bl;
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx_read;
+ expect_context_complete(finish_ctx_read, 0);
+ Extents image_extents_read{{0, 4096}};
+ bufferlist read_bl;
+ rwl.read(std::move(image_extents_read), &read_bl, fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(0, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ ASSERT_TRUE(bl_copy.contents_equal(read_bl));
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, read_hit_part_rwl_cache) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ bufferlist bl_copy = bl;
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx_read;
+ Extents image_extents_read{{512, 4096}};
+ bufferlist hit_bl;
+ bl_copy.begin(511).copy(4096-512, hit_bl);
+ expect_context_complete(finish_ctx_read, 512);
+ bufferlist read_bl;
+ rwl.read(std::move(image_extents_read), &read_bl, fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(512, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ bufferlist read_bl_hit;
+ read_bl.begin(0).copy(4096-512, read_bl_hit);
+ ASSERT_TRUE(hit_bl.contents_equal(read_bl_hit));
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, read_miss_rwl_cache) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx_read;
+ Extents image_extents_read{{4096, 4096}};
+ expect_context_complete(finish_ctx_read, 4096);
+ bufferlist read_bl;
+ ASSERT_EQ(0, read_bl.length());
+ rwl.read(std::move(image_extents_read), &read_bl, fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(4096, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, discard) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ bufferlist bl_copy = bl;
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx_discard;
+ expect_context_complete(finish_ctx_discard, 0);
+ rwl.discard(0, 4096, 1, &finish_ctx_discard);
+ ASSERT_EQ(0, finish_ctx_discard.wait());
+
+ MockContextRWL finish_ctx_read;
+ bufferlist read_bl;
+ expect_context_complete(finish_ctx_read, 0);
+ rwl.read({{0, 4096}}, &read_bl, fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(0, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ ASSERT_TRUE(read_bl.is_zero());
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, writesame) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ bufferlist bl, test_bl;
+ bl.append(std::string(512, '1'));
+ test_bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ rwl.writesame(0, 4096, std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx_read;
+ bufferlist read_bl;
+ expect_context_complete(finish_ctx_read, 0);
+ rwl.read({{0, 4096}}, &read_bl, fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(0, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ ASSERT_TRUE(test_bl.contents_equal(read_bl));
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, invalidate) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ bufferlist bl_copy = bl;
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx_invalidate;
+ expect_context_complete(finish_ctx_invalidate, 0);
+ rwl.invalidate(&finish_ctx_invalidate);
+ ASSERT_EQ(0, finish_ctx_invalidate.wait());
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, compare_and_write_compare_matched) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl1;
+ bl1.append(std::string(4096, '1'));
+ bufferlist com_bl = bl1;
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl1), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx_cw;
+ bufferlist bl2;
+ bl2.append(std::string(4096, '2'));
+ bufferlist bl2_copy = bl2;
+ uint64_t mismatch_offset = -1;
+ expect_context_complete(finish_ctx_cw, 0);
+ rwl.compare_and_write({{0, 4096}}, std::move(com_bl), std::move(bl2),
+ &mismatch_offset, fadvise_flags, &finish_ctx_cw);
+ ASSERT_EQ(0, finish_ctx_cw.wait());
+ ASSERT_EQ(0, mismatch_offset);
+
+ MockContextRWL finish_ctx_read;
+ bufferlist read_bl;
+ expect_context_complete(finish_ctx_read, 0);
+ rwl.read({{0, 4096}}, &read_bl, fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(0, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ ASSERT_TRUE(bl2_copy.contents_equal(read_bl));
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheReplicatedWriteLog, compare_and_write_compare_failed) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockReplicatedWriteLog rwl(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextRWL finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ rwl.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextRWL finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl1;
+ bl1.append(std::string(4096, '1'));
+ bufferlist bl1_copy = bl1;
+ int fadvise_flags = 0;
+ rwl.write(std::move(image_extents), std::move(bl1), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextRWL finish_ctx_cw;
+ bufferlist bl2;
+ bl2.append(std::string(4096, '2'));
+ bufferlist com_bl = bl2;
+ uint64_t mismatch_offset = -1;
+ expect_context_complete(finish_ctx_cw, -EILSEQ);
+ rwl.compare_and_write({{0, 4096}}, std::move(com_bl), std::move(bl2),
+ &mismatch_offset, fadvise_flags, &finish_ctx_cw);
+ ASSERT_EQ(-EILSEQ, finish_ctx_cw.wait());
+ ASSERT_EQ(0, mismatch_offset);
+
+ MockContextRWL finish_ctx_read;
+ bufferlist read_bl;
+ expect_context_complete(finish_ctx_read, 0);
+ rwl.read({{0, 4096}}, &read_bl, fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(0, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ ASSERT_TRUE(bl1_copy.contents_equal(read_bl));
+
+ MockContextRWL finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ rwl.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+} // namespace pwl
+} // namespace cache
+} // namespace librbd
diff --git a/src/test/librbd/cache/pwl/test_mock_SSDWriteLog.cc b/src/test/librbd/cache/pwl/test_mock_SSDWriteLog.cc
new file mode 100644
index 000000000..72a44dcc9
--- /dev/null
+++ b/src/test/librbd/cache/pwl/test_mock_SSDWriteLog.cc
@@ -0,0 +1,761 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <iostream>
+#include "common/hostname.h"
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "include/rbd/librbd.hpp"
+#include "librbd/cache/pwl/AbstractWriteLog.h"
+#include "librbd/cache/pwl/ImageCacheState.h"
+#include "librbd/cache/pwl/Types.h"
+#include "librbd/cache/ImageWriteback.h"
+#include "librbd/plugin/Api.h"
+
+namespace librbd {
+namespace {
+
+struct MockContextSSD : public C_SaferCond {
+ MOCK_METHOD1(complete, void(int));
+ MOCK_METHOD1(finish, void(int));
+
+ void do_complete(int r) {
+ C_SaferCond::complete(r);
+ }
+};
+
+} // anonymous namespace
+
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+#include "librbd/cache/pwl/AbstractWriteLog.cc"
+#include "librbd/cache/pwl/ssd/WriteLog.cc"
+template class librbd::cache::pwl::ssd::WriteLog<librbd::MockImageCtx>;
+
+// template definitions
+#include "librbd/cache/ImageWriteback.cc"
+#include "librbd/cache/pwl/ImageCacheState.cc"
+#include "librbd/cache/pwl/Request.cc"
+#include "librbd/plugin/Api.cc"
+#include "librbd/cache/pwl/ssd/Request.cc"
+
+namespace librbd {
+namespace cache {
+namespace pwl {
+
+using ::testing::_;
+using ::testing::DoDefault;
+using ::testing::InSequence;
+using ::testing::Invoke;
+
+typedef io::Extent Extent;
+typedef io::Extents Extents;
+
+struct TestMockCacheSSDWriteLog : public TestMockFixture {
+ typedef librbd::cache::pwl::ssd::WriteLog<librbd::MockImageCtx> MockSSDWriteLog;
+ typedef librbd::cache::pwl::ImageCacheState<librbd::MockImageCtx> MockImageCacheStateSSD;
+ typedef librbd::cache::ImageWriteback<librbd::MockImageCtx> MockImageWriteback;
+ typedef librbd::plugin::Api<librbd::MockImageCtx> MockApi;
+
+ MockImageCacheStateSSD *get_cache_state(
+ MockImageCtx& mock_image_ctx, MockApi& mock_api) {
+ MockImageCacheStateSSD *ssd_state = new MockImageCacheStateSSD(
+ &mock_image_ctx, mock_api);
+ return ssd_state;
+ }
+
+ void validate_cache_state(librbd::ImageCtx *image_ctx,
+ MockImageCacheStateSSD &state,
+ bool present, bool empty, bool clean,
+ string host, string path,
+ uint64_t size) {
+ ASSERT_EQ(present, state.present);
+ ASSERT_EQ(empty, state.empty);
+ ASSERT_EQ(clean, state.clean);
+
+ ASSERT_EQ(host, state.host);
+ ASSERT_EQ(path, state.path);
+ ASSERT_EQ(size, state.size);
+ }
+
+ void expect_context_complete(MockContextSSD& mock_context, int r) {
+ EXPECT_CALL(mock_context, complete(r))
+ .WillRepeatedly(Invoke([&mock_context](int r) {
+ mock_context.do_complete(r);
+ }));
+ }
+
+ void expect_metadata_set(MockImageCtx& mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.operations, execute_metadata_set(_, _, _))
+ .WillRepeatedly(Invoke([](std::string key, std::string val, Context* ctx) {
+ ctx->complete(0);
+ }));
+ }
+
+ void expect_metadata_remove(MockImageCtx& mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.operations, execute_metadata_remove(_, _))
+ .WillRepeatedly(Invoke([](std::string key, Context* ctx) {
+ ctx->complete(0);
+ }));
+ }
+};
+
+TEST_F(TestMockCacheSSDWriteLog, init_state_write) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockApi mock_api;
+ MockImageCacheStateSSD image_cache_state(&mock_image_ctx, mock_api);
+
+ validate_cache_state(ictx, image_cache_state, false, true, true, "", "", 0);
+
+ image_cache_state.empty = false;
+ image_cache_state.clean = false;
+ ceph::mutex lock = ceph::make_mutex("MockImageCacheStateSSD lock");
+ MockContextSSD finish_ctx;
+ expect_metadata_set(mock_image_ctx);
+ expect_context_complete(finish_ctx, 0);
+ std::unique_lock locker(lock);
+ image_cache_state.write_image_cache_state(locker, &finish_ctx);
+ ASSERT_FALSE(locker.owns_lock());
+ ASSERT_EQ(0, finish_ctx.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, init_state_json_write) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockApi mock_api;
+ MockImageCacheStateSSD image_cache_state(&mock_image_ctx, mock_api);
+
+ string strf = "{ \"present\": true, \"empty\": false, \"clean\": false, \
+ \"host\": \"testhost\", \
+ \"path\": \"/tmp\", \
+ \"mode\": \"ssd\", \
+ \"size\": 1024 }";
+ json_spirit::mValue json_root;
+ ASSERT_TRUE(json_spirit::read(strf.c_str(), json_root));
+ ASSERT_TRUE(image_cache_state.init_from_metadata(json_root));
+ validate_cache_state(ictx, image_cache_state, true, false, false,
+ "testhost", "/tmp", 1024);
+
+ MockContextSSD finish_ctx;
+ expect_metadata_remove(mock_image_ctx);
+ expect_context_complete(finish_ctx, 0);
+ image_cache_state.clear_image_cache_state(&finish_ctx);
+ ASSERT_EQ(0, finish_ctx.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, init_shutdown) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ MockContextSSD finish_ctx1;
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ ssd.shut_down(&finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, write) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+
+ MockContextSSD finish_ctx1;
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, read_hit_ssd_cache) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ bufferlist bl_copy = bl;
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl),
+ fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx_read;
+ expect_context_complete(finish_ctx_read, 0);
+ Extents image_extents_read{{0, 4096}};
+ bufferlist read_bl;
+ ssd.read(std::move(image_extents_read), &read_bl,
+ fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(0, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ ASSERT_TRUE(bl_copy.contents_equal(read_bl));
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, read_hit_part_ssd_cache) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 8192}};
+ bufferlist bl;
+ bl.append(std::string(8192, '1'));
+ bufferlist bl_copy = bl;
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl),
+ fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx_read;
+ Extents image_extents_read{{4096, 4096}};
+ bufferlist hit_bl;
+ bl_copy.begin(4095).copy(4096, hit_bl);
+ expect_context_complete(finish_ctx_read, 0);
+ bufferlist read_bl;
+ ssd.read(std::move(image_extents_read), &read_bl,
+ fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(0, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ bufferlist read_bl_hit;
+ read_bl.begin(0).copy(4096, read_bl_hit);
+ ASSERT_TRUE(hit_bl.contents_equal(read_bl_hit));
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, read_miss_ssd_cache) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl),
+ fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx_read;
+ Extents image_extents_read{{4096, 4096}};
+ expect_context_complete(finish_ctx_read, 4096);
+ bufferlist read_bl;
+ ASSERT_EQ(0, read_bl.length());
+ ssd.read(std::move(image_extents_read), &read_bl,
+ fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(4096, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, compare_and_write_compare_matched) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl1;
+ bl1.append(std::string(4096, '1'));
+ bufferlist com_bl = bl1;
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl1), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx_cw;
+ bufferlist bl2;
+ bl2.append(std::string(4096, '2'));
+ bufferlist bl2_copy = bl2;
+ uint64_t mismatch_offset = -1;
+ expect_context_complete(finish_ctx_cw, 0);
+ ssd.compare_and_write({{0, 4096}}, std::move(com_bl), std::move(bl2),
+ &mismatch_offset, fadvise_flags, &finish_ctx_cw);
+ ASSERT_EQ(0, finish_ctx_cw.wait());
+ ASSERT_EQ(0, mismatch_offset);
+
+ MockContextSSD finish_ctx_read;
+ bufferlist read_bl;
+ expect_context_complete(finish_ctx_read, 0);
+ ssd.read({{0, 4096}}, &read_bl, fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(0, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ ASSERT_TRUE(bl2_copy.contents_equal(read_bl));
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, compare_and_write_compare_failed) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl1;
+ bl1.append(std::string(4096, '1'));
+ bufferlist bl1_copy = bl1;
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl1), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx_cw;
+ bufferlist bl2;
+ bl2.append(std::string(4096, '2'));
+ bufferlist com_bl = bl2;
+ uint64_t mismatch_offset = -1;
+ expect_context_complete(finish_ctx_cw, -EILSEQ);
+ ssd.compare_and_write({{0, 4096}}, std::move(com_bl), std::move(bl2),
+ &mismatch_offset, fadvise_flags, &finish_ctx_cw);
+ ASSERT_EQ(-EILSEQ, finish_ctx_cw.wait());
+ ASSERT_EQ(0, mismatch_offset);
+
+ MockContextSSD finish_ctx_read;
+ bufferlist read_bl;
+ expect_context_complete(finish_ctx_read, 0);
+ ssd.read({{0, 4096}}, &read_bl, fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(0, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ ASSERT_TRUE(bl1_copy.contents_equal(read_bl));
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, writesame) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ bufferlist bl, test_bl;
+ bl.append(std::string(512, '1'));
+ test_bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ ssd.writesame(0, 4096, std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx_read;
+ bufferlist read_bl;
+ expect_context_complete(finish_ctx_read, 0);
+ ssd.read({{0, 4096}}, &read_bl, fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(0, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ ASSERT_TRUE(test_bl.contents_equal(read_bl));
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, discard) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ bufferlist bl_copy = bl;
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx_discard;
+ expect_context_complete(finish_ctx_discard, 0);
+ ssd.discard(0, 4096, 1, &finish_ctx_discard);
+ ASSERT_EQ(0, finish_ctx_discard.wait());
+
+ MockContextSSD finish_ctx_read;
+ bufferlist read_bl;
+ expect_context_complete(finish_ctx_read, 0);
+ ssd.read({{0, 4096}}, &read_bl, fadvise_flags, &finish_ctx_read);
+ ASSERT_EQ(0, finish_ctx_read.wait());
+ ASSERT_EQ(4096, read_bl.length());
+ ASSERT_TRUE(read_bl.is_zero());
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, invalidate) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ bufferlist bl_copy = bl;
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx_invalidate;
+ expect_context_complete(finish_ctx_invalidate, 0);
+ ssd.invalidate(&finish_ctx_invalidate);
+ ASSERT_EQ(0, finish_ctx_invalidate.wait());
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, flush) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ bufferlist bl_copy = bl;
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx_flush;
+ expect_context_complete(finish_ctx_flush, 0);
+ ssd.flush(&finish_ctx_flush);
+ ASSERT_EQ(0, finish_ctx_flush.wait());
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, flush_source_shutdown) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx_flush;
+ expect_context_complete(finish_ctx_flush, 0);
+ ssd.flush(io::FLUSH_SOURCE_SHUTDOWN, &finish_ctx_flush);
+ ASSERT_EQ(0, finish_ctx_flush.wait());
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+
+TEST_F(TestMockCacheSSDWriteLog, flush_source_internal) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ MockContextSSD finish_ctx_flush;
+ expect_context_complete(finish_ctx_flush, 0);
+ ssd.flush(io::FLUSH_SOURCE_INTERNAL, &finish_ctx_flush);
+ ASSERT_EQ(0, finish_ctx_flush.wait());
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ ssd.shut_down(&finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheSSDWriteLog, flush_source_user) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ MockImageWriteback mock_image_writeback(mock_image_ctx);
+ MockApi mock_api;
+ MockSSDWriteLog ssd(
+ mock_image_ctx, get_cache_state(mock_image_ctx, mock_api),
+ mock_image_writeback, mock_api);
+ expect_op_work_queue(mock_image_ctx);
+ expect_metadata_set(mock_image_ctx);
+
+ MockContextSSD finish_ctx1;
+ expect_context_complete(finish_ctx1, 0);
+ ssd.init(&finish_ctx1);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContextSSD finish_ctx2;
+ expect_context_complete(finish_ctx2, 0);
+ Extents image_extents{{0, 4096}};
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+ int fadvise_flags = 0;
+ ssd.write(std::move(image_extents), std::move(bl), fadvise_flags, &finish_ctx2);
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ usleep(10000);
+ MockContextSSD finish_ctx_flush;
+ expect_context_complete(finish_ctx_flush, 0);
+ ssd.flush(io::FLUSH_SOURCE_USER, &finish_ctx_flush);
+ ASSERT_EQ(0, finish_ctx_flush.wait());
+
+ MockContextSSD finish_ctx3;
+ expect_context_complete(finish_ctx3, 0);
+ Extents image_extents2{{0, 4096}};
+ bufferlist bl2;
+ bl2.append(std::string(4096, '1'));
+ int fadvise_flags2 = 0;
+ ssd.write(std::move(image_extents2), std::move(bl2), fadvise_flags2, &finish_ctx3);
+ ASSERT_EQ(0, finish_ctx3.wait());
+
+ MockContextSSD finish_ctx4;
+ expect_context_complete(finish_ctx4, 0);
+ ssd.shut_down(&finish_ctx4);
+ ASSERT_EQ(0, finish_ctx4.wait());
+}
+
+} // namespace pwl
+} // namespace cache
+} // namespace librbd
diff --git a/src/test/librbd/cache/test_mock_ParentCacheObjectDispatch.cc b/src/test/librbd/cache/test_mock_ParentCacheObjectDispatch.cc
new file mode 100644
index 000000000..05e56f520
--- /dev/null
+++ b/src/test/librbd/cache/test_mock_ParentCacheObjectDispatch.cc
@@ -0,0 +1,427 @@
+// -*- 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 "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "include/Context.h"
+#include "tools/immutable_object_cache/CacheClient.h"
+#include "test/immutable_object_cache/MockCacheDaemon.h"
+#include "librbd/cache/ParentCacheObjectDispatch.h"
+#include "librbd/plugin/Api.h"
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/mock/MockImageCtx.h"
+
+using namespace ceph::immutable_obj_cache;
+
+namespace librbd {
+
+namespace {
+
+struct MockParentImageCacheImageCtx : public MockImageCtx {
+ MockParentImageCacheImageCtx(ImageCtx& image_ctx)
+ : MockImageCtx(image_ctx) {
+ }
+ ~MockParentImageCacheImageCtx() {}
+};
+
+} // anonymous namespace
+
+namespace cache {
+
+template<>
+struct TypeTraits<MockParentImageCacheImageCtx> {
+ typedef ceph::immutable_obj_cache::MockCacheClient CacheClient;
+};
+
+} // namespace cache
+
+namespace plugin {
+
+template <>
+struct Api<MockParentImageCacheImageCtx> {
+ MOCK_METHOD6(read_parent, void(MockParentImageCacheImageCtx*, uint64_t,
+ librbd::io::ReadExtents*, librados::snap_t,
+ const ZTracer::Trace &, Context*));
+};
+
+} // namespace plugin
+} // namespace librbd
+
+#include "librbd/cache/ParentCacheObjectDispatch.cc"
+template class librbd::cache::ParentCacheObjectDispatch<librbd::MockParentImageCacheImageCtx>;
+
+namespace librbd {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+
+class TestMockParentCacheObjectDispatch : public TestMockFixture {
+public :
+ typedef cache::ParentCacheObjectDispatch<librbd::MockParentImageCacheImageCtx> MockParentImageCache;
+ typedef plugin::Api<MockParentImageCacheImageCtx> MockPluginApi;
+
+ // ====== mock cache client ====
+ void expect_cache_run(MockParentImageCache& mparent_image_cache, bool ret_val) {
+ auto& expect = EXPECT_CALL(*(mparent_image_cache.get_cache_client()), run());
+
+ expect.WillOnce((Invoke([]() {
+ })));
+ }
+
+ void expect_cache_session_state(MockParentImageCache& mparent_image_cache, bool ret_val) {
+ auto & expect = EXPECT_CALL(*(mparent_image_cache.get_cache_client()), is_session_work());
+
+ expect.WillOnce((Invoke([ret_val]() {
+ return ret_val;
+ })));
+ }
+
+ void expect_cache_connect(MockParentImageCache& mparent_image_cache, int ret_val) {
+ auto& expect = EXPECT_CALL(*(mparent_image_cache.get_cache_client()), connect());
+
+ expect.WillOnce((Invoke([ret_val]() {
+ return ret_val;
+ })));
+ }
+
+ void expect_cache_async_connect(MockParentImageCache& mparent_image_cache, int ret_val,
+ Context* on_finish) {
+ auto& expect = EXPECT_CALL(*(mparent_image_cache.get_cache_client()), connect(_));
+
+ expect.WillOnce(WithArg<0>(Invoke([on_finish, ret_val](Context* ctx) {
+ ctx->complete(ret_val);
+ on_finish->complete(ret_val);
+ })));
+ }
+
+ void expect_cache_lookup_object(MockParentImageCache& mparent_image_cache,
+ const std::string &cache_path) {
+ EXPECT_CALL(*(mparent_image_cache.get_cache_client()),
+ lookup_object(_, _, _, _, _, _))
+ .WillOnce(WithArg<5>(Invoke([cache_path](CacheGenContextURef on_finish) {
+ ObjectCacheReadReplyData ack(RBDSC_READ_REPLY, 0, cache_path);
+ on_finish.release()->complete(&ack);
+ })));
+ }
+
+ void expect_read_parent(MockPluginApi &mock_plugin_api, uint64_t object_no,
+ io::ReadExtents* extents, librados::snap_t snap_id,
+ int r) {
+ EXPECT_CALL(mock_plugin_api,
+ read_parent(_, object_no, extents, snap_id, _, _))
+ .WillOnce(WithArg<5>(CompleteContext(r, static_cast<asio::ContextWQ*>(nullptr))));
+ }
+
+ void expect_cache_close(MockParentImageCache& mparent_image_cache, int ret_val) {
+ auto& expect = EXPECT_CALL(*(mparent_image_cache.get_cache_client()), close());
+
+ expect.WillOnce((Invoke([]() {
+ })));
+ }
+
+ void expect_cache_stop(MockParentImageCache& mparent_image_cache, int ret_val) {
+ auto& expect = EXPECT_CALL(*(mparent_image_cache.get_cache_client()), stop());
+
+ expect.WillOnce((Invoke([]() {
+ })));
+ }
+
+ void expect_cache_register(MockParentImageCache& mparent_image_cache, Context* mock_handle_register, int ret_val) {
+ auto& expect = EXPECT_CALL(*(mparent_image_cache.get_cache_client()), register_client(_));
+
+ expect.WillOnce(WithArg<0>(Invoke([mock_handle_register, ret_val](Context* ctx) {
+ if(ret_val == 0) {
+ mock_handle_register->complete(true);
+ } else {
+ mock_handle_register->complete(false);
+ }
+ ctx->complete(true);
+ return ret_val;
+ })));
+ }
+
+ void expect_io_object_dispatcher_register_state(MockParentImageCache& mparent_image_cache,
+ int ret_val) {
+ auto& expect = EXPECT_CALL((*(mparent_image_cache.get_image_ctx()->io_object_dispatcher)),
+ register_dispatch(_));
+
+ expect.WillOnce(WithArg<0>(Invoke([&mparent_image_cache]
+ (io::ObjectDispatchInterface* object_dispatch) {
+ ASSERT_EQ(object_dispatch, &mparent_image_cache);
+ })));
+ }
+};
+
+TEST_F(TestMockParentCacheObjectDispatch, test_initialization_success) {
+ librbd::ImageCtx* ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ MockParentImageCacheImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.child = &mock_image_ctx;
+
+ MockPluginApi mock_plugin_api;
+ auto mock_parent_image_cache = MockParentImageCache::create(&mock_image_ctx,
+ mock_plugin_api);
+
+ expect_cache_run(*mock_parent_image_cache, 0);
+ C_SaferCond cond;
+ Context* handle_connect = new LambdaContext([&cond](int ret) {
+ ASSERT_EQ(ret, 0);
+ cond.complete(0);
+ });
+ expect_cache_async_connect(*mock_parent_image_cache, 0, handle_connect);
+ Context* ctx = new LambdaContext([](bool reg) {
+ ASSERT_EQ(reg, true);
+ });
+ expect_cache_register(*mock_parent_image_cache, ctx, 0);
+ expect_io_object_dispatcher_register_state(*mock_parent_image_cache, 0);
+ expect_cache_close(*mock_parent_image_cache, 0);
+ expect_cache_stop(*mock_parent_image_cache, 0);
+
+ mock_parent_image_cache->init();
+ cond.wait();
+
+ ASSERT_EQ(mock_parent_image_cache->get_dispatch_layer(),
+ io::OBJECT_DISPATCH_LAYER_PARENT_CACHE);
+ expect_cache_session_state(*mock_parent_image_cache, true);
+ ASSERT_EQ(mock_parent_image_cache->get_cache_client()->is_session_work(), true);
+
+ mock_parent_image_cache->get_cache_client()->close();
+ mock_parent_image_cache->get_cache_client()->stop();
+
+ delete mock_parent_image_cache;
+}
+
+TEST_F(TestMockParentCacheObjectDispatch, test_initialization_fail_at_connect) {
+ librbd::ImageCtx* ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ MockParentImageCacheImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.child = &mock_image_ctx;
+
+ MockPluginApi mock_plugin_api;
+ auto mock_parent_image_cache = MockParentImageCache::create(&mock_image_ctx,
+ mock_plugin_api);
+
+ expect_cache_run(*mock_parent_image_cache, 0);
+ C_SaferCond cond;
+ Context* handle_connect = new LambdaContext([&cond](int ret) {
+ ASSERT_EQ(ret, -1);
+ cond.complete(0);
+ });
+ expect_cache_async_connect(*mock_parent_image_cache, -1, handle_connect);
+ expect_io_object_dispatcher_register_state(*mock_parent_image_cache, 0);
+ expect_cache_session_state(*mock_parent_image_cache, false);
+ expect_cache_close(*mock_parent_image_cache, 0);
+ expect_cache_stop(*mock_parent_image_cache, 0);
+
+ mock_parent_image_cache->init();
+
+ // initialization fails.
+ ASSERT_EQ(mock_parent_image_cache->get_dispatch_layer(),
+ io::OBJECT_DISPATCH_LAYER_PARENT_CACHE);
+ ASSERT_EQ(mock_parent_image_cache->get_cache_client()->is_session_work(), false);
+
+ mock_parent_image_cache->get_cache_client()->close();
+ mock_parent_image_cache->get_cache_client()->stop();
+
+ delete mock_parent_image_cache;
+
+}
+
+TEST_F(TestMockParentCacheObjectDispatch, test_initialization_fail_at_register) {
+ librbd::ImageCtx* ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ MockParentImageCacheImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.child = &mock_image_ctx;
+
+ MockPluginApi mock_plugin_api;
+ auto mock_parent_image_cache = MockParentImageCache::create(&mock_image_ctx,
+ mock_plugin_api);
+
+ expect_cache_run(*mock_parent_image_cache, 0);
+ C_SaferCond cond;
+ Context* handle_connect = new LambdaContext([&cond](int ret) {
+ ASSERT_EQ(ret, 0);
+ cond.complete(0);
+ });
+ expect_cache_async_connect(*mock_parent_image_cache, 0, handle_connect);
+ Context* ctx = new LambdaContext([](bool reg) {
+ ASSERT_EQ(reg, false);
+ });
+ expect_cache_register(*mock_parent_image_cache, ctx, -1);
+ expect_io_object_dispatcher_register_state(*mock_parent_image_cache, 0);
+ expect_cache_close(*mock_parent_image_cache, 0);
+ expect_cache_stop(*mock_parent_image_cache, 0);
+
+ mock_parent_image_cache->init();
+ cond.wait();
+
+ ASSERT_EQ(mock_parent_image_cache->get_dispatch_layer(),
+ io::OBJECT_DISPATCH_LAYER_PARENT_CACHE);
+ expect_cache_session_state(*mock_parent_image_cache, true);
+ ASSERT_EQ(mock_parent_image_cache->get_cache_client()->is_session_work(), true);
+
+ mock_parent_image_cache->get_cache_client()->close();
+ mock_parent_image_cache->get_cache_client()->stop();
+
+ delete mock_parent_image_cache;
+}
+
+TEST_F(TestMockParentCacheObjectDispatch, test_disble_interface) {
+ librbd::ImageCtx* ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ MockParentImageCacheImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.child = &mock_image_ctx;
+
+ MockPluginApi mock_plugin_api;
+ auto mock_parent_image_cache = MockParentImageCache::create(&mock_image_ctx,
+ mock_plugin_api);
+
+ std::string temp_oid("12345");
+ ceph::bufferlist temp_bl;
+ IOContext io_context = mock_image_ctx.get_data_io_context();
+ io::DispatchResult* temp_dispatch_result = nullptr;
+ io::Extents temp_buffer_extents;
+ int* temp_op_flags = nullptr;
+ uint64_t* temp_journal_tid = nullptr;
+ Context** temp_on_finish = nullptr;
+ Context* temp_on_dispatched = nullptr;
+ ZTracer::Trace* temp_trace = nullptr;
+ io::LightweightBufferExtents buffer_extents;
+
+ ASSERT_EQ(mock_parent_image_cache->discard(0, 0, 0, io_context, 0,
+ *temp_trace, temp_op_flags, temp_journal_tid, temp_dispatch_result,
+ temp_on_finish, temp_on_dispatched), false);
+ ASSERT_EQ(mock_parent_image_cache->write(0, 0, std::move(temp_bl),
+ io_context, 0, 0, std::nullopt, *temp_trace, temp_op_flags,
+ temp_journal_tid, temp_dispatch_result, temp_on_finish,
+ temp_on_dispatched), false);
+ ASSERT_EQ(mock_parent_image_cache->write_same(0, 0, 0, std::move(buffer_extents),
+ std::move(temp_bl), io_context, 0, *temp_trace, temp_op_flags,
+ temp_journal_tid, temp_dispatch_result, temp_on_finish, temp_on_dispatched), false );
+ ASSERT_EQ(mock_parent_image_cache->compare_and_write(0, 0, std::move(temp_bl), std::move(temp_bl),
+ io_context, 0, *temp_trace, temp_journal_tid, temp_op_flags,
+ temp_journal_tid, temp_dispatch_result, temp_on_finish,
+ temp_on_dispatched), false);
+ ASSERT_EQ(mock_parent_image_cache->flush(io::FLUSH_SOURCE_USER, *temp_trace, temp_journal_tid,
+ temp_dispatch_result, temp_on_finish, temp_on_dispatched), false);
+ ASSERT_EQ(mock_parent_image_cache->invalidate_cache(nullptr), false);
+ ASSERT_EQ(mock_parent_image_cache->reset_existence_cache(nullptr), false);
+
+ delete mock_parent_image_cache;
+
+}
+
+TEST_F(TestMockParentCacheObjectDispatch, test_read) {
+ librbd::ImageCtx* ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ MockParentImageCacheImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.child = &mock_image_ctx;
+
+ MockPluginApi mock_plugin_api;
+ auto mock_parent_image_cache = MockParentImageCache::create(&mock_image_ctx,
+ mock_plugin_api);
+
+ expect_cache_run(*mock_parent_image_cache, 0);
+ C_SaferCond conn_cond;
+ Context* handle_connect = new LambdaContext([&conn_cond](int ret) {
+ ASSERT_EQ(ret, 0);
+ conn_cond.complete(0);
+ });
+ expect_cache_async_connect(*mock_parent_image_cache, 0, handle_connect);
+ Context* ctx = new LambdaContext([](bool reg) {
+ ASSERT_EQ(reg, true);
+ });
+ expect_cache_register(*mock_parent_image_cache, ctx, 0);
+ expect_io_object_dispatcher_register_state(*mock_parent_image_cache, 0);
+ expect_cache_close(*mock_parent_image_cache, 0);
+ expect_cache_stop(*mock_parent_image_cache, 0);
+
+ mock_parent_image_cache->init();
+ conn_cond.wait();
+
+ ASSERT_EQ(mock_parent_image_cache->get_dispatch_layer(),
+ io::OBJECT_DISPATCH_LAYER_PARENT_CACHE);
+ expect_cache_session_state(*mock_parent_image_cache, true);
+ ASSERT_EQ(mock_parent_image_cache->get_cache_client()->is_session_work(), true);
+
+ auto& expect = EXPECT_CALL(*(mock_parent_image_cache->get_cache_client()), is_session_work());
+ expect.WillOnce(Return(true));
+
+ expect_cache_lookup_object(*mock_parent_image_cache, "/dev/null");
+
+ C_SaferCond on_dispatched;
+ io::DispatchResult dispatch_result;
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ mock_parent_image_cache->read(
+ 0, &extents, mock_image_ctx.get_data_io_context(), 0, 0, {}, nullptr,
+ nullptr, &dispatch_result, nullptr, &on_dispatched);
+ ASSERT_EQ(0, on_dispatched.wait());
+
+ mock_parent_image_cache->get_cache_client()->close();
+ mock_parent_image_cache->get_cache_client()->stop();
+ delete mock_parent_image_cache;
+}
+
+TEST_F(TestMockParentCacheObjectDispatch, test_read_dne) {
+ librbd::ImageCtx* ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ MockParentImageCacheImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.child = &mock_image_ctx;
+
+ MockPluginApi mock_plugin_api;
+ auto mock_parent_image_cache = MockParentImageCache::create(&mock_image_ctx,
+ mock_plugin_api);
+
+ expect_cache_run(*mock_parent_image_cache, 0);
+ C_SaferCond conn_cond;
+ Context* handle_connect = new LambdaContext([&conn_cond](int ret) {
+ ASSERT_EQ(ret, 0);
+ conn_cond.complete(0);
+ });
+ expect_cache_async_connect(*mock_parent_image_cache, 0, handle_connect);
+ Context* ctx = new LambdaContext([](bool reg) {
+ ASSERT_EQ(reg, true);
+ });
+ expect_cache_register(*mock_parent_image_cache, ctx, 0);
+ expect_io_object_dispatcher_register_state(*mock_parent_image_cache, 0);
+ expect_cache_close(*mock_parent_image_cache, 0);
+ expect_cache_stop(*mock_parent_image_cache, 0);
+
+ mock_parent_image_cache->init();
+ conn_cond.wait();
+
+ ASSERT_EQ(mock_parent_image_cache->get_dispatch_layer(),
+ io::OBJECT_DISPATCH_LAYER_PARENT_CACHE);
+ expect_cache_session_state(*mock_parent_image_cache, true);
+ ASSERT_EQ(mock_parent_image_cache->get_cache_client()->is_session_work(),
+ true);
+
+ EXPECT_CALL(*(mock_parent_image_cache->get_cache_client()), is_session_work())
+ .WillOnce(Return(true));
+
+ expect_cache_lookup_object(*mock_parent_image_cache, "");
+
+ io::ReadExtents extents = {{0, 4096}};
+ expect_read_parent(mock_plugin_api, 0, &extents, CEPH_NOSNAP, 0);
+
+ C_SaferCond on_dispatched;
+ io::DispatchResult dispatch_result;
+ mock_parent_image_cache->read(
+ 0, &extents, mock_image_ctx.get_data_io_context(), 0, 0, {}, nullptr,
+ nullptr, &dispatch_result, nullptr, &on_dispatched);
+ ASSERT_EQ(0, on_dispatched.wait());
+
+ mock_parent_image_cache->get_cache_client()->close();
+ mock_parent_image_cache->get_cache_client()->stop();
+ delete mock_parent_image_cache;
+}
+
+} // namespace librbd
diff --git a/src/test/librbd/cache/test_mock_WriteAroundObjectDispatch.cc b/src/test/librbd/cache/test_mock_WriteAroundObjectDispatch.cc
new file mode 100644
index 000000000..abfd185e3
--- /dev/null
+++ b/src/test/librbd/cache/test_mock_WriteAroundObjectDispatch.cc
@@ -0,0 +1,703 @@
+// -*- 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 "include/rbd/librbd.hpp"
+#include "librbd/cache/WriteAroundObjectDispatch.h"
+#include "librbd/io/ObjectDispatchSpec.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+struct MockContext : public C_SaferCond {
+ MOCK_METHOD1(complete, void(int));
+ MOCK_METHOD1(finish, void(int));
+
+ void do_complete(int r) {
+ C_SaferCond::complete(r);
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+#include "librbd/cache/WriteAroundObjectDispatch.cc"
+
+namespace librbd {
+namespace cache {
+
+using ::testing::_;
+using ::testing::DoDefault;
+using ::testing::InSequence;
+using ::testing::Invoke;
+
+struct TestMockCacheWriteAroundObjectDispatch : public TestMockFixture {
+ typedef WriteAroundObjectDispatch<librbd::MockTestImageCtx> MockWriteAroundObjectDispatch;
+
+ void expect_op_work_queue(MockTestImageCtx& mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.op_work_queue, queue(_, _))
+ .WillRepeatedly(Invoke([](Context* ctx, int r) {
+ ctx->complete(r);
+ }));
+ }
+
+ void expect_context_complete(MockContext& mock_context, int r) {
+ EXPECT_CALL(mock_context, complete(r))
+ .WillOnce(Invoke([&mock_context](int r) {
+ mock_context.do_complete(r);
+ }));
+ }
+};
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, WriteThrough) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 0, false);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx;
+ MockContext dispatch_ctx;
+ Context* finish_ctx_ptr = &finish_ctx;
+ ASSERT_FALSE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr,
+ &dispatch_ctx));
+ ASSERT_EQ(finish_ctx_ptr, &finish_ctx);
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, WriteThroughUntilFlushed) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 4096, true);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx;
+ MockContext dispatch_ctx;
+ Context* finish_ctx_ptr = &finish_ctx;
+ ASSERT_FALSE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr,
+ &dispatch_ctx));
+ ASSERT_EQ(finish_ctx_ptr, &finish_ctx);
+
+ ASSERT_FALSE(object_dispatch.flush(io::FLUSH_SOURCE_USER, {}, nullptr,
+ &dispatch_result, &finish_ctx_ptr,
+ &dispatch_ctx));
+
+ expect_context_complete(dispatch_ctx, 0);
+ expect_context_complete(finish_ctx, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr,
+ &dispatch_ctx));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr, &finish_ctx);
+ ASSERT_EQ(0, dispatch_ctx.wait());
+ ASSERT_EQ(0, finish_ctx.wait());
+ finish_ctx_ptr->complete(0);
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, DispatchIO) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 4096, false);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx;
+ MockContext dispatch_ctx;
+ Context* finish_ctx_ptr = &finish_ctx;
+
+ expect_context_complete(dispatch_ctx, 0);
+ expect_context_complete(finish_ctx, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr,
+ &dispatch_ctx));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr, &finish_ctx);
+
+ ASSERT_EQ(0, dispatch_ctx.wait());
+ ASSERT_EQ(0, finish_ctx.wait());
+ finish_ctx_ptr->complete(0);
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, BlockedIO) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 16384, false);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx1;
+ MockContext dispatch_ctx1;
+ Context* finish_ctx_ptr1 = &finish_ctx1;
+
+ expect_context_complete(dispatch_ctx1, 0);
+ expect_context_complete(finish_ctx1, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt,{}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr1,
+ &dispatch_ctx1));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
+
+ MockContext finish_ctx2;
+ MockContext dispatch_ctx2;
+ Context* finish_ctx_ptr2 = &finish_ctx2;
+
+ expect_context_complete(dispatch_ctx2, 0);
+ expect_context_complete(finish_ctx2, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 4096, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr2,
+ &dispatch_ctx2));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
+
+ MockContext finish_ctx3;
+ MockContext dispatch_ctx3;
+ Context* finish_ctx_ptr3 = &finish_ctx3;
+
+ ASSERT_TRUE(object_dispatch.write(0, 1024, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr3,
+ &dispatch_ctx3));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr3, &finish_ctx3);
+
+ ASSERT_EQ(0, dispatch_ctx1.wait());
+ ASSERT_EQ(0, dispatch_ctx2.wait());
+ ASSERT_EQ(0, finish_ctx1.wait());
+ ASSERT_EQ(0, finish_ctx2.wait());
+ finish_ctx_ptr2->complete(0);
+
+ expect_context_complete(dispatch_ctx3, 0);
+ expect_context_complete(finish_ctx3, 0);
+ finish_ctx_ptr1->complete(0);
+
+ ASSERT_EQ(0, dispatch_ctx3.wait());
+ finish_ctx_ptr3->complete(0);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, QueuedIO) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 4095, false);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx1;
+ MockContext dispatch_ctx1;
+ Context* finish_ctx_ptr1 = &finish_ctx1;
+
+ expect_context_complete(dispatch_ctx1, 0);
+ expect_context_complete(finish_ctx1, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr1,
+ &dispatch_ctx1));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
+
+ MockContext finish_ctx2;
+ MockContext dispatch_ctx2;
+ Context* finish_ctx_ptr2 = &finish_ctx2;
+
+ ASSERT_TRUE(object_dispatch.write(0, 8192, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr2,
+ &dispatch_ctx2));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
+ ASSERT_EQ(0, dispatch_ctx1.wait());
+
+ expect_context_complete(dispatch_ctx2, 0);
+ expect_context_complete(finish_ctx2, 0);
+ finish_ctx_ptr1->complete(0);
+
+ ASSERT_EQ(0, finish_ctx1.wait());
+ ASSERT_EQ(0, dispatch_ctx2.wait());
+ ASSERT_EQ(0, finish_ctx2.wait());
+ finish_ctx_ptr2->complete(0);
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, BlockedAndQueuedIO) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 8196, false);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx1;
+ MockContext dispatch_ctx1;
+ Context* finish_ctx_ptr1 = &finish_ctx1;
+
+ expect_context_complete(dispatch_ctx1, 0);
+ expect_context_complete(finish_ctx1, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr1,
+ &dispatch_ctx1));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
+
+ MockContext finish_ctx2;
+ MockContext dispatch_ctx2;
+ Context* finish_ctx_ptr2 = &finish_ctx2;
+
+ expect_context_complete(dispatch_ctx2, 0);
+ expect_context_complete(finish_ctx2, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 4096, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr2,
+ &dispatch_ctx2));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
+
+ MockContext finish_ctx3;
+ MockContext dispatch_ctx3;
+ Context* finish_ctx_ptr3 = &finish_ctx3;
+
+ ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr3,
+ &dispatch_ctx3));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr3, &finish_ctx3);
+
+ ASSERT_EQ(0, dispatch_ctx1.wait());
+ ASSERT_EQ(0, dispatch_ctx2.wait());
+ ASSERT_EQ(0, finish_ctx1.wait());
+ ASSERT_EQ(0, finish_ctx2.wait());
+ finish_ctx_ptr2->complete(0);
+
+ expect_context_complete(dispatch_ctx3, 0);
+ expect_context_complete(finish_ctx3, 0);
+ finish_ctx_ptr1->complete(0);
+
+ ASSERT_EQ(0, dispatch_ctx3.wait());
+ ASSERT_EQ(0, finish_ctx3.wait());
+ finish_ctx_ptr3->complete(0);
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, Flush) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 4096, true);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx;
+ MockContext dispatch_ctx;
+ Context* finish_ctx_ptr = &finish_ctx;
+ ASSERT_FALSE(object_dispatch.flush(io::FLUSH_SOURCE_USER, {}, nullptr,
+ &dispatch_result, &finish_ctx_ptr,
+ &dispatch_ctx));
+ ASSERT_EQ(finish_ctx_ptr, &finish_ctx);
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, FlushQueuedOnInFlightIO) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 4096, false);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx1;
+ MockContext dispatch_ctx1;
+ Context* finish_ctx_ptr1 = &finish_ctx1;
+
+ expect_context_complete(dispatch_ctx1, 0);
+ expect_context_complete(finish_ctx1, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr1,
+ &dispatch_ctx1));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
+ ASSERT_EQ(0, dispatch_ctx1.wait());
+
+ MockContext finish_ctx2;
+ MockContext dispatch_ctx2;
+ Context* finish_ctx_ptr2 = &finish_ctx2;
+ ASSERT_FALSE(object_dispatch.flush(io::FLUSH_SOURCE_USER, {}, nullptr,
+ &dispatch_result, &finish_ctx_ptr2,
+ &dispatch_ctx2));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
+
+ expect_context_complete(finish_ctx2, 0);
+ finish_ctx_ptr1->complete(0);
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ finish_ctx_ptr2->complete(0);
+ ASSERT_EQ(0, finish_ctx2.wait());
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, FlushQueuedOnQueuedIO) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 4096, false);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx1;
+ MockContext dispatch_ctx1;
+ Context* finish_ctx_ptr1 = &finish_ctx1;
+
+ expect_context_complete(dispatch_ctx1, 0);
+ expect_context_complete(finish_ctx1, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr1,
+ &dispatch_ctx1));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
+ ASSERT_EQ(0, dispatch_ctx1.wait());
+
+ MockContext finish_ctx2;
+ MockContext dispatch_ctx2;
+ Context* finish_ctx_ptr2 = &finish_ctx2;
+
+ ASSERT_TRUE(object_dispatch.write(0, 8192, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr2,
+ &dispatch_ctx2));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
+ ASSERT_EQ(0, dispatch_ctx1.wait());
+
+ MockContext finish_ctx3;
+ MockContext dispatch_ctx3;
+ Context* finish_ctx_ptr3 = &finish_ctx3;
+ ASSERT_TRUE(object_dispatch.flush(io::FLUSH_SOURCE_USER, {}, nullptr,
+ &dispatch_result, &finish_ctx_ptr3,
+ &dispatch_ctx3));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr3, &finish_ctx3);
+
+ expect_context_complete(dispatch_ctx2, 0);
+ expect_context_complete(finish_ctx2, 0);
+ expect_context_complete(dispatch_ctx3, 0);
+ finish_ctx_ptr1->complete(0);
+
+ ASSERT_EQ(0, finish_ctx1.wait());
+ ASSERT_EQ(0, dispatch_ctx2.wait());
+ ASSERT_EQ(0, finish_ctx2.wait());
+
+ expect_context_complete(finish_ctx3, 0);
+ finish_ctx_ptr2->complete(0);
+
+ finish_ctx_ptr3->complete(0);
+ ASSERT_EQ(0, finish_ctx3.wait());
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, FlushError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 4096, false);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx1;
+ MockContext dispatch_ctx1;
+ Context* finish_ctx_ptr1 = &finish_ctx1;
+
+ expect_context_complete(dispatch_ctx1, 0);
+ expect_context_complete(finish_ctx1, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr1,
+ &dispatch_ctx1));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
+ ASSERT_EQ(0, dispatch_ctx1.wait());
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContext finish_ctx2;
+ MockContext dispatch_ctx2;
+ Context* finish_ctx_ptr2 = &finish_ctx2;
+ ASSERT_FALSE(object_dispatch.flush(io::FLUSH_SOURCE_USER, {}, nullptr,
+ &dispatch_result, &finish_ctx_ptr2,
+ &dispatch_ctx2));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
+
+ expect_context_complete(finish_ctx2, -EPERM);
+ finish_ctx_ptr1->complete(-EPERM);
+ finish_ctx_ptr2->complete(0);
+ ASSERT_EQ(-EPERM, finish_ctx2.wait());
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, UnoptimizedIO) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 16384, false);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx;
+ MockContext dispatch_ctx;
+ Context* finish_ctx_ptr = &finish_ctx;
+
+ ASSERT_FALSE(object_dispatch.compare_and_write(0, 0, std::move(data),
+ std::move(data), {}, 0, {},
+ nullptr, nullptr, nullptr,
+ &dispatch_result,
+ &finish_ctx_ptr,
+ &dispatch_ctx));
+ ASSERT_EQ(finish_ctx_ptr, &finish_ctx);
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, UnoptimizedIOInFlightIO) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 16384, false);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx1;
+ MockContext dispatch_ctx1;
+ Context* finish_ctx_ptr1 = &finish_ctx1;
+
+ expect_context_complete(dispatch_ctx1, 0);
+ expect_context_complete(finish_ctx1, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr1,
+ &dispatch_ctx1));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
+ ASSERT_EQ(0, dispatch_ctx1.wait());
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContext finish_ctx2;
+ MockContext dispatch_ctx2;
+ Context* finish_ctx_ptr2 = &finish_ctx2;
+ ASSERT_TRUE(object_dispatch.compare_and_write(0, 0, std::move(data),
+ std::move(data), {}, 0, {},
+ nullptr, nullptr, nullptr,
+ &dispatch_result,
+ &finish_ctx_ptr2,
+ &dispatch_ctx2));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_EQ(finish_ctx_ptr2, &finish_ctx2);
+
+ expect_context_complete(dispatch_ctx2, 0);
+ finish_ctx_ptr1->complete(0);
+ ASSERT_EQ(0, dispatch_ctx2.wait());
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, UnoptimizedIOBlockedIO) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 4096, false);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx1;
+ MockContext dispatch_ctx1;
+ Context* finish_ctx_ptr1 = &finish_ctx1;
+
+ expect_context_complete(dispatch_ctx1, 0);
+ expect_context_complete(finish_ctx1, 0);
+
+ ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr1,
+ &dispatch_ctx1));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
+ ASSERT_EQ(0, dispatch_ctx1.wait());
+ ASSERT_EQ(0, finish_ctx1.wait());
+
+ MockContext finish_ctx2;
+ MockContext dispatch_ctx2;
+ Context* finish_ctx_ptr2 = &finish_ctx2;
+ ASSERT_TRUE(object_dispatch.write(0, 4096, std::move(data), {}, 0, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr2,
+ &dispatch_ctx2));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
+
+ MockContext finish_ctx3;
+ MockContext dispatch_ctx3;
+ Context* finish_ctx_ptr3 = &finish_ctx3;
+ ASSERT_TRUE(object_dispatch.compare_and_write(0, 0, std::move(data),
+ std::move(data), {}, 0, {},
+ nullptr, nullptr, nullptr,
+ &dispatch_result,
+ &finish_ctx_ptr3,
+ &dispatch_ctx3));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_EQ(finish_ctx_ptr3, &finish_ctx3);
+
+ expect_context_complete(dispatch_ctx3, 0);
+ expect_context_complete(dispatch_ctx2, 0);
+ expect_context_complete(finish_ctx2, 0);
+ finish_ctx_ptr1->complete(0);
+ ASSERT_EQ(0, dispatch_ctx3.wait());
+ ASSERT_EQ(0, dispatch_ctx2.wait());
+ ASSERT_EQ(0, finish_ctx2.wait());
+ finish_ctx_ptr2->complete(0);
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, WriteFUA) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 16384, false);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(4096, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx;
+ MockContext dispatch_ctx;
+ Context* finish_ctx_ptr = &finish_ctx;
+ ASSERT_FALSE(object_dispatch.write(0, 0, std::move(data), {},
+ LIBRADOS_OP_FLAG_FADVISE_FUA, 0,
+ std::nullopt, {}, nullptr, nullptr,
+ &dispatch_result, &finish_ctx_ptr,
+ &dispatch_ctx));
+ ASSERT_EQ(finish_ctx_ptr, &finish_ctx);
+}
+
+TEST_F(TestMockCacheWriteAroundObjectDispatch, WriteSameFUA) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockWriteAroundObjectDispatch object_dispatch(&mock_image_ctx, 16384, false);
+
+ InSequence seq;
+
+ bufferlist data;
+ data.append(std::string(512, '1'));
+
+ io::DispatchResult dispatch_result;
+ MockContext finish_ctx;
+ MockContext dispatch_ctx;
+ Context* finish_ctx_ptr = &finish_ctx;
+ ASSERT_FALSE(object_dispatch.write_same(0, 0, 8192, {{0, 8192}},
+ std::move(data), {},
+ LIBRADOS_OP_FLAG_FADVISE_FUA, {},
+ nullptr, nullptr, &dispatch_result,
+ &finish_ctx_ptr, &dispatch_ctx));
+ ASSERT_EQ(finish_ctx_ptr, &finish_ctx);
+}
+
+} // namespace cache
+} // namespace librbd
diff --git a/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc b/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc
new file mode 100644
index 000000000..d27c3fe12
--- /dev/null
+++ b/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc
@@ -0,0 +1,195 @@
+// -*- 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"
+
+namespace librbd {
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+#include "librbd/crypto/luks/FormatRequest.cc"
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+
+struct TestMockCryptoLuksFormatRequest : public TestMockFixture {
+ typedef FormatRequest<librbd::MockImageCtx> MockFormatRequest;
+
+ const size_t OBJECT_SIZE = 4 * 1024 * 1024;
+ const size_t IMAGE_SIZE = 1024 * 1024 * 1024;
+ const char* passphrase_cstr = "password";
+ std::string passphrase = passphrase_cstr;
+
+ MockImageCtx* mock_image_ctx;
+ C_SaferCond finished_cond;
+ Context *on_finish = &finished_cond;
+ io::AioCompletion* aio_comp;
+ ceph::bufferlist header_bl;
+ ceph::ref_t<CryptoInterface> crypto;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ mock_image_ctx = new MockImageCtx(*ictx);
+ crypto = nullptr;
+ }
+
+ void TearDown() override {
+ if (crypto != nullptr) {
+ crypto->put();
+ crypto = nullptr;
+ }
+ delete mock_image_ctx;
+ TestMockFixture::TearDown();
+ }
+
+ void expect_get_object_size() {
+ EXPECT_CALL(*mock_image_ctx, get_object_size()).WillOnce(Return(
+ OBJECT_SIZE));
+ }
+
+ void expect_get_image_size(uint64_t image_size) {
+ EXPECT_CALL(*mock_image_ctx, get_image_size(CEPH_NOSNAP)).WillOnce(Return(
+ image_size));
+ }
+
+ void expect_image_write() {
+ EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_))
+ .WillOnce(Invoke([this](io::ImageDispatchSpec* spec) {
+ auto* write = boost::get<io::ImageDispatchSpec::Write>(
+ &spec->request);
+ ASSERT_TRUE(write != nullptr);
+
+ ASSERT_EQ(1, spec->image_extents.size());
+ ASSERT_EQ(0, spec->image_extents[0].first);
+ ASSERT_GT(spec->image_extents[0].second, 0);
+
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ aio_comp = spec->aio_comp;
+ header_bl = write->bl;
+ }));
+ }
+
+ void complete_aio(int r) {
+ if (r < 0) {
+ aio_comp->fail(r);
+ } else {
+ aio_comp->set_request_count(1);
+ aio_comp->add_request();
+ aio_comp->complete_request(r);
+ }
+ }
+
+ void verify_header(const char* expected_format, size_t expected_key_length,
+ uint64_t expected_sector_size) {
+ Header header(mock_image_ctx->cct);
+
+ ASSERT_EQ(0, header.init());
+ ASSERT_EQ(0, header.write(header_bl));
+ ASSERT_EQ(0, header.load(expected_format));
+
+ ASSERT_EQ(expected_sector_size, header.get_sector_size());
+ ASSERT_EQ(0, header.get_data_offset() % OBJECT_SIZE);
+
+ char volume_key[64];
+ size_t volume_key_size = sizeof(volume_key);
+ ASSERT_EQ(0, header.read_volume_key(
+ passphrase_cstr, strlen(passphrase_cstr),
+ reinterpret_cast<char*>(volume_key), &volume_key_size));
+
+ ASSERT_EQ(expected_key_length, crypto->get_key_length());
+ ASSERT_EQ(0, std::memcmp(
+ volume_key, crypto->get_key(), expected_key_length));
+ ASSERT_EQ(expected_sector_size, crypto->get_block_size());
+ ASSERT_EQ(header.get_data_offset(), crypto->get_data_offset());
+ }
+};
+
+TEST_F(TestMockCryptoLuksFormatRequest, LUKS1) {
+ auto mock_format_request = MockFormatRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1,
+ RBD_ENCRYPTION_ALGORITHM_AES128, std::move(passphrase), &crypto,
+ on_finish, true);
+ expect_get_object_size();
+ expect_get_image_size(IMAGE_SIZE);
+ expect_image_write();
+ mock_format_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ complete_aio(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS1, 32, 512));
+}
+
+TEST_F(TestMockCryptoLuksFormatRequest, AES128) {
+ auto mock_format_request = MockFormatRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2,
+ RBD_ENCRYPTION_ALGORITHM_AES128, std::move(passphrase), &crypto,
+ on_finish, true);
+ expect_get_object_size();
+ expect_get_image_size(IMAGE_SIZE);
+ expect_image_write();
+ mock_format_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ complete_aio(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS2, 32, 4096));
+}
+
+TEST_F(TestMockCryptoLuksFormatRequest, AES256) {
+ auto mock_format_request = MockFormatRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2,
+ RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto,
+ on_finish, true);
+ expect_get_object_size();
+ expect_get_image_size(IMAGE_SIZE);
+ expect_image_write();
+ mock_format_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ complete_aio(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS2, 64, 4096));
+}
+
+TEST_F(TestMockCryptoLuksFormatRequest, ImageTooSmall) {
+ auto mock_format_request = MockFormatRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2,
+ RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto,
+ on_finish, true);
+ expect_get_object_size();
+ expect_get_image_size(1024*1024);
+ mock_format_request->send();
+ ASSERT_EQ(-ENOSPC, finished_cond.wait());
+}
+
+TEST_F(TestMockCryptoLuksFormatRequest, WriteFail) {
+ auto mock_format_request = MockFormatRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2,
+ RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto,
+ on_finish, true);
+ expect_get_object_size();
+ expect_get_image_size(IMAGE_SIZE);
+ expect_image_write();
+ mock_format_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ complete_aio(-123);
+ ASSERT_EQ(-123, finished_cond.wait());
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
diff --git a/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc b/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc
new file mode 100644
index 000000000..1a2300616
--- /dev/null
+++ b/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc
@@ -0,0 +1,221 @@
+// -*- 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"
+
+namespace librbd {
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+#include "librbd/crypto/luks/LoadRequest.cc"
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+
+struct TestMockCryptoLuksLoadRequest : public TestMockFixture {
+ typedef LoadRequest<librbd::MockImageCtx> MockLoadRequest;
+
+ const size_t OBJECT_SIZE = 4 * 1024 * 1024;
+ const char* passphrase_cstr = "password";
+ std::string passphrase = passphrase_cstr;
+
+ MockImageCtx* mock_image_ctx;
+ ceph::ref_t<CryptoInterface> crypto;
+ MockLoadRequest* mock_load_request;
+ C_SaferCond finished_cond;
+ Context *on_finish = &finished_cond;
+ Context* image_read_request;
+ ceph::bufferlist header_bl;
+ uint64_t data_offset;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ mock_image_ctx = new MockImageCtx(*ictx);
+ crypto = nullptr;
+ mock_load_request = MockLoadRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, std::move(passphrase),
+ &crypto, on_finish);
+ }
+
+ void TearDown() override {
+ delete mock_image_ctx;
+ if (crypto != nullptr) {
+ crypto->put();
+ crypto = nullptr;
+ }
+ TestMockFixture::TearDown();
+ }
+
+ // returns data offset in bytes
+ void generate_header(const char* type, const char* alg, size_t key_size,
+ const char* cipher_mode, uint32_t sector_size) {
+ Header header(mock_image_ctx->cct);
+
+ ASSERT_EQ(0, header.init());
+ ASSERT_EQ(0, header.format(type, alg, nullptr, key_size, cipher_mode,
+ sector_size, OBJECT_SIZE, true));
+ ASSERT_EQ(0, header.add_keyslot(passphrase_cstr, strlen(passphrase_cstr)));
+ ASSERT_LE(0, header.read(&header_bl));
+
+ data_offset = header.get_data_offset();
+ }
+
+ void expect_image_read(uint64_t offset, uint64_t length) {
+ EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_))
+ .WillOnce(Invoke([this, offset,
+ length](io::ImageDispatchSpec* spec) {
+ auto* read = boost::get<io::ImageDispatchSpec::Read>(
+ &spec->request);
+ ASSERT_TRUE(read != nullptr);
+
+ ASSERT_EQ(1, spec->image_extents.size());
+ ASSERT_EQ(offset, spec->image_extents[0].first);
+ ASSERT_EQ(length, spec->image_extents[0].second);
+
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto aio_comp = spec->aio_comp;
+ aio_comp->set_request_count(1);
+ aio_comp->read_result = std::move(read->read_result);
+ aio_comp->read_result.set_image_extents(spec->image_extents);
+ auto ctx = new io::ReadResult::C_ImageReadRequest(
+ aio_comp, 0, spec->image_extents);
+ if (header_bl.length() < offset + length) {
+ header_bl.append_zero(offset + length - header_bl.length());
+ }
+ ctx->bl.substr_of(header_bl, offset, length);
+ image_read_request = ctx;
+ }));
+ }
+};
+
+TEST_F(TestMockCryptoLuksLoadRequest, AES128) {
+ generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NE(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, AES256) {
+ generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NE(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, LUKS1) {
+ delete mock_load_request;
+ mock_load_request = MockLoadRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1, {passphrase_cstr},
+ &crypto, on_finish);
+ generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NE(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, WrongFormat) {
+ generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ mock_load_request->send();
+
+ expect_image_read(DEFAULT_INITIAL_READ_SIZE,
+ MAXIMUM_HEADER_SIZE - DEFAULT_INITIAL_READ_SIZE);
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); // complete 1st read
+
+ image_read_request->complete(
+ MAXIMUM_HEADER_SIZE - DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(-EINVAL, finished_cond.wait());
+ ASSERT_EQ(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedAlgorithm) {
+ generate_header(CRYPT_LUKS2, "twofish", 32, "xts-plain64", 4096);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(-ENOTSUP, finished_cond.wait());
+ ASSERT_EQ(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedCipherMode) {
+ generate_header(CRYPT_LUKS2, "aes", 32, "cbc-essiv:sha256", 4096);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(-ENOTSUP, finished_cond.wait());
+ ASSERT_EQ(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, HeaderBiggerThanInitialRead) {
+ generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
+ mock_load_request->set_initial_read_size(4096);
+ expect_image_read(0, 4096);
+ mock_load_request->send();
+
+ expect_image_read(4096, MAXIMUM_HEADER_SIZE - 4096);
+ image_read_request->complete(4096); // complete initial read
+
+ image_read_request->complete(MAXIMUM_HEADER_SIZE - 4096);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NE(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, KeyslotsBiggerThanInitialRead) {
+ generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
+ mock_load_request->set_initial_read_size(16384);
+ expect_image_read(0, 16384);
+ mock_load_request->send();
+
+ expect_image_read(16384, data_offset - 16384);
+ image_read_request->complete(16384); // complete initial read
+
+ image_read_request->complete(data_offset - 16384);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NE(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, WrongPassphrase) {
+ delete mock_load_request;
+ mock_load_request = MockLoadRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, "wrong", &crypto,
+ on_finish);
+
+ generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ mock_load_request->send();
+
+ // crypt_volume_key_get will fail, we will retry reading more
+ expect_image_read(DEFAULT_INITIAL_READ_SIZE,
+ data_offset - DEFAULT_INITIAL_READ_SIZE);
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+
+ image_read_request->complete(data_offset - DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(-EPERM, finished_cond.wait());
+ ASSERT_EQ(crypto, nullptr);
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
diff --git a/src/test/librbd/crypto/openssl/test_DataCryptor.cc b/src/test/librbd/crypto/openssl/test_DataCryptor.cc
new file mode 100644
index 000000000..a3ba4c883
--- /dev/null
+++ b/src/test/librbd/crypto/openssl/test_DataCryptor.cc
@@ -0,0 +1,118 @@
+// -*- 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 "librbd/crypto/openssl/DataCryptor.h"
+
+namespace librbd {
+namespace crypto {
+namespace openssl {
+
+const char* TEST_CIPHER_NAME = "aes-256-xts";
+const unsigned char TEST_KEY[64] = {1};
+const unsigned char TEST_IV[16] = {2};
+const unsigned char TEST_IV_2[16] = {3};
+const unsigned char TEST_DATA[4096] = {4};
+
+struct TestCryptoOpensslDataCryptor : public TestFixture {
+ DataCryptor *cryptor;
+
+ void SetUp() override {
+ TestFixture::SetUp();
+ cryptor = new DataCryptor(reinterpret_cast<CephContext*>(m_ioctx.cct()));
+ ASSERT_EQ(0,
+ cryptor->init(TEST_CIPHER_NAME, TEST_KEY, sizeof(TEST_KEY)));
+ }
+
+ void TearDown() override {
+ delete cryptor;
+ TestFixture::TearDown();
+ }
+};
+
+TEST_F(TestCryptoOpensslDataCryptor, InvalidCipherName) {
+ EXPECT_EQ(-EINVAL, cryptor->init(nullptr, TEST_KEY, sizeof(TEST_KEY)));
+ EXPECT_EQ(-EINVAL, cryptor->init("", TEST_KEY, sizeof(TEST_KEY)));
+ EXPECT_EQ(-EINVAL, cryptor->init("Invalid", TEST_KEY, sizeof(TEST_KEY)));
+}
+
+TEST_F(TestCryptoOpensslDataCryptor, InvalidKey) {
+ EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, nullptr, 0));
+ EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, nullptr,
+ sizeof(TEST_KEY)));
+ EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, TEST_KEY, 1));
+}
+
+TEST_F(TestCryptoOpensslDataCryptor, GetContextInvalidMode) {
+ EXPECT_EQ(nullptr, cryptor->get_context(static_cast<CipherMode>(-1)));
+}
+
+TEST_F(TestCryptoOpensslDataCryptor, ReturnNullContext) {
+ cryptor->return_context(nullptr, static_cast<CipherMode>(-1));
+}
+
+TEST_F(TestCryptoOpensslDataCryptor, ReturnContextInvalidMode) {
+ auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+ ASSERT_NE(ctx, nullptr);
+ cryptor->return_context(ctx, CipherMode::CIPHER_MODE_DEC);
+ ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+ ASSERT_NE(ctx, nullptr);
+ cryptor->return_context(ctx, static_cast<CipherMode>(-1));
+}
+
+TEST_F(TestCryptoOpensslDataCryptor, EncryptDecrypt) {
+ auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+ ASSERT_NE(ctx, nullptr);
+ cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV));
+
+ unsigned char out[sizeof(TEST_DATA)];
+ ASSERT_EQ(sizeof(TEST_DATA),
+ cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA)));
+ cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC);
+ ctx = cryptor->get_context(CipherMode::CIPHER_MODE_DEC);
+ ASSERT_NE(ctx, nullptr);
+ ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV)));
+ ASSERT_EQ(sizeof(TEST_DATA),
+ cryptor->update_context(ctx, out, out, sizeof(TEST_DATA)));
+ ASSERT_EQ(0, memcmp(out, TEST_DATA, sizeof(TEST_DATA)));
+ cryptor->return_context(ctx, CipherMode::CIPHER_MODE_DEC);
+}
+
+TEST_F(TestCryptoOpensslDataCryptor, ReuseContext) {
+ auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+ ASSERT_NE(ctx, nullptr);
+
+ ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV)));
+ unsigned char out[sizeof(TEST_DATA)];
+ ASSERT_EQ(sizeof(TEST_DATA),
+ cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA)));
+
+ ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV_2, sizeof(TEST_IV_2)));
+ ASSERT_EQ(sizeof(TEST_DATA),
+ cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA)));
+
+ auto ctx2 = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+ ASSERT_NE(ctx2, nullptr);
+
+ ASSERT_EQ(0, cryptor->init_context(ctx2, TEST_IV_2, sizeof(TEST_IV_2)));
+ unsigned char out2[sizeof(TEST_DATA)];
+ ASSERT_EQ(sizeof(TEST_DATA),
+ cryptor->update_context(ctx2, TEST_DATA, out2, sizeof(TEST_DATA)));
+
+ ASSERT_EQ(0, memcmp(out, out2, sizeof(TEST_DATA)));
+
+ cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC);
+ cryptor->return_context(ctx2, CipherMode::CIPHER_MODE_ENC);
+}
+
+TEST_F(TestCryptoOpensslDataCryptor, InvalidIVLength) {
+ auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+ ASSERT_NE(ctx, nullptr);
+
+ ASSERT_EQ(-EINVAL, cryptor->init_context(ctx, TEST_IV, 1));
+ cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC);
+}
+
+} // namespace openssl
+} // namespace crypto
+} // namespace librbd
diff --git a/src/test/librbd/crypto/test_mock_BlockCrypto.cc b/src/test/librbd/crypto/test_mock_BlockCrypto.cc
new file mode 100644
index 000000000..07dc19437
--- /dev/null
+++ b/src/test/librbd/crypto/test_mock_BlockCrypto.cc
@@ -0,0 +1,156 @@
+// -*- 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 "librbd/crypto/BlockCrypto.h"
+#include "test/librbd/mock/crypto/MockDataCryptor.h"
+
+#include "librbd/crypto/BlockCrypto.cc"
+template class librbd::crypto::BlockCrypto<
+ librbd::crypto::MockCryptoContext>;
+
+using ::testing::ExpectationSet;
+using ::testing::internal::ExpectationBase;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::WithArg;
+using ::testing::_;
+
+namespace librbd {
+namespace crypto {
+
+MATCHER_P(CompareArrayToString, s, "") {
+ return (memcmp(arg, s.c_str(), s.length()) == 0);
+}
+
+struct TestMockCryptoBlockCrypto : public TestFixture {
+ MockDataCryptor* cryptor;
+ ceph::ref_t<BlockCrypto<MockCryptoContext>> bc;
+ int cryptor_block_size = 16;
+ int cryptor_iv_size = 16;
+ int block_size = 4096;
+ int data_offset = 0;
+ ExpectationSet* expectation_set;
+
+ void SetUp() override {
+ TestFixture::SetUp();
+
+ cryptor = new MockDataCryptor();
+ cryptor->block_size = cryptor_block_size;
+ bc = new BlockCrypto<MockCryptoContext>(
+ reinterpret_cast<CephContext*>(m_ioctx.cct()), cryptor,
+ block_size, data_offset);
+ expectation_set = new ExpectationSet();
+ }
+
+ void TearDown() override {
+ delete expectation_set;
+ bc->put();
+ TestFixture::TearDown();
+ }
+
+ void expect_get_context(CipherMode mode) {
+ _set_last_expectation(
+ EXPECT_CALL(*cryptor, get_context(mode))
+ .After(*expectation_set).WillOnce(Return(
+ new MockCryptoContext())));
+ }
+
+ void expect_return_context(CipherMode mode) {
+ _set_last_expectation(
+ EXPECT_CALL(*cryptor, return_context(_, mode))
+ .After(*expectation_set).WillOnce(WithArg<0>(
+ Invoke([](MockCryptoContext* ctx) {
+ delete ctx;
+ }))));
+ }
+
+ void expect_init_context(const std::string& iv) {
+ _set_last_expectation(
+ EXPECT_CALL(*cryptor, init_context(_, CompareArrayToString(iv),
+ cryptor_iv_size))
+ .After(*expectation_set));
+ }
+
+ void expect_update_context(const std::string& in_str, int out_ret) {
+ _set_last_expectation(
+ EXPECT_CALL(*cryptor, update_context(_,
+ CompareArrayToString(in_str),
+ _, in_str.length()))
+ .After(*expectation_set).WillOnce(Return(out_ret)));
+ }
+
+ void _set_last_expectation(ExpectationBase& expectation) {
+ delete expectation_set;
+ expectation_set = new ExpectationSet(expectation);
+ }
+};
+
+TEST_F(TestMockCryptoBlockCrypto, Encrypt) {
+ uint32_t image_offset = 0x1230 * 512;
+
+ ceph::bufferlist data1;
+ data1.append(std::string(2048, '1'));
+ ceph::bufferlist data2;
+ data2.append(std::string(4096, '2'));
+ ceph::bufferlist data3;
+ data3.append(std::string(2048, '3'));
+
+ ceph::bufferlist data;
+ data.claim_append(data1);
+ data.claim_append(data2);
+ data.claim_append(data3);
+
+ expect_get_context(CipherMode::CIPHER_MODE_ENC);
+ expect_init_context(std::string("\x30\x12\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16));
+ expect_update_context(std::string(2048, '1') + std::string(2048, '2'), 4096);
+ expect_init_context(std::string("\x38\x12\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16));
+ expect_update_context(std::string(2048, '2') + std::string(2048, '3'), 4096);
+ expect_return_context(CipherMode::CIPHER_MODE_ENC);
+
+ ASSERT_EQ(0, bc->encrypt(&data, image_offset));
+
+ ASSERT_EQ(data.length(), 8192);
+}
+
+TEST_F(TestMockCryptoBlockCrypto, UnalignedImageOffset) {
+ ceph::bufferlist data;
+ data.append(std::string(4096, '1'));
+ ASSERT_EQ(-EINVAL, bc->encrypt(&data, 2));
+}
+
+TEST_F(TestMockCryptoBlockCrypto, UnalignedDataLength) {
+ ceph::bufferlist data;
+ data.append(std::string(512, '1'));
+ ASSERT_EQ(-EINVAL, bc->encrypt(&data, 0));
+}
+
+TEST_F(TestMockCryptoBlockCrypto, GetContextError) {
+ ceph::bufferlist data;
+ data.append(std::string(4096, '1'));
+ EXPECT_CALL(*cryptor, get_context(CipherMode::CIPHER_MODE_ENC)).WillOnce(
+ Return(nullptr));
+ ASSERT_EQ(-EIO, bc->encrypt(&data, 0));
+}
+
+TEST_F(TestMockCryptoBlockCrypto, InitContextError) {
+ ceph::bufferlist data;
+ data.append(std::string(4096, '1'));
+ expect_get_context(CipherMode::CIPHER_MODE_ENC);
+ EXPECT_CALL(*cryptor, init_context(_, _, _)).WillOnce(Return(-123));
+ expect_return_context(CipherMode::CIPHER_MODE_ENC);
+ ASSERT_EQ(-123, bc->encrypt(&data, 0));
+}
+
+TEST_F(TestMockCryptoBlockCrypto, UpdateContextError) {
+ ceph::bufferlist data;
+ data.append(std::string(4096, '1'));
+ expect_get_context(CipherMode::CIPHER_MODE_ENC);
+ EXPECT_CALL(*cryptor, init_context(_, _, _));
+ EXPECT_CALL(*cryptor, update_context(_, _, _, _)).WillOnce(Return(-123));
+ expect_return_context(CipherMode::CIPHER_MODE_ENC);
+ ASSERT_EQ(-123, bc->encrypt(&data, 0));
+}
+
+} // namespace crypto
+} // namespace librbd
diff --git a/src/test/librbd/crypto/test_mock_CryptoContextPool.cc b/src/test/librbd/crypto/test_mock_CryptoContextPool.cc
new file mode 100644
index 000000000..6eb7877eb
--- /dev/null
+++ b/src/test/librbd/crypto/test_mock_CryptoContextPool.cc
@@ -0,0 +1,54 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "gtest/gtest.h"
+#include "librbd/crypto/CryptoContextPool.h"
+#include "test/librbd/mock/crypto/MockDataCryptor.h"
+
+#include "librbd/crypto/CryptoContextPool.cc"
+template class librbd::crypto::CryptoContextPool<
+ librbd::crypto::MockCryptoContext>;
+
+using ::testing::Return;
+
+namespace librbd {
+namespace crypto {
+
+struct TestMockCryptoCryptoContextPool : public ::testing::Test {
+ MockDataCryptor cryptor;
+
+ void expect_get_context(CipherMode mode) {
+ EXPECT_CALL(cryptor, get_context(mode)).WillOnce(Return(
+ new MockCryptoContext()));
+ }
+
+ void expect_return_context(MockCryptoContext* ctx, CipherMode mode) {
+ delete ctx;
+ EXPECT_CALL(cryptor, return_context(ctx, mode));
+ }
+};
+
+TEST_F(TestMockCryptoCryptoContextPool, Test) {
+ CryptoContextPool<MockCryptoContext> pool(&cryptor, 1);
+
+ expect_get_context(CipherMode::CIPHER_MODE_ENC);
+ auto enc_ctx = pool.get_context(CipherMode::CIPHER_MODE_ENC);
+
+ expect_get_context(CipherMode::CIPHER_MODE_DEC);
+ auto dec_ctx1 = pool.get_context(CipherMode::CIPHER_MODE_DEC);
+ expect_get_context(CipherMode::CIPHER_MODE_DEC);
+ auto dec_ctx2 = pool.get_context(CipherMode::CIPHER_MODE_DEC);
+ pool.return_context(dec_ctx1, CipherMode::CIPHER_MODE_DEC);
+ expect_return_context(dec_ctx2, CipherMode::CIPHER_MODE_DEC);
+ pool.return_context(dec_ctx2, CipherMode::CIPHER_MODE_DEC);
+
+ pool.return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC);
+ ASSERT_EQ(enc_ctx, pool.get_context(CipherMode::CIPHER_MODE_ENC));
+ pool.return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC);
+
+ expect_return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC);
+ expect_return_context(dec_ctx1, CipherMode::CIPHER_MODE_DEC);
+}
+
+} // namespace crypto
+} // namespace librbd
diff --git a/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc b/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc
new file mode 100644
index 000000000..04b6b8962
--- /dev/null
+++ b/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc
@@ -0,0 +1,798 @@
+// -*- 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/crypto/MockCryptoInterface.h"
+#include "librbd/crypto/CryptoObjectDispatch.h"
+#include "librbd/io/ObjectDispatchSpec.h"
+#include "librbd/io/Utils.h"
+
+#include "librbd/io/Utils.cc"
+template bool librbd::io::util::trigger_copyup(
+ MockImageCtx *image_ctx, uint64_t object_no, IOContext io_context,
+ Context* on_finish);
+
+template class librbd::io::ObjectWriteRequest<librbd::MockImageCtx>;
+template class librbd::io::AbstractObjectWriteRequest<librbd::MockImageCtx>;
+#include "librbd/io/ObjectRequest.cc"
+
+namespace librbd {
+
+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_METHOD2(append_request, void(
+ AbstractObjectWriteRequest<librbd::MockImageCtx>*,
+ const Extents&));
+
+ static CopyupRequest* s_instance;
+ static CopyupRequest* create(librbd::MockImageCtx *ictx,
+ uint64_t objectno, Extents &&image_extents,
+ const ZTracer::Trace &parent_trace) {
+ return s_instance;
+ }
+
+ CopyupRequest() {
+ s_instance = this;
+ }
+};
+
+CopyupRequest<librbd::MockImageCtx>* CopyupRequest<
+ librbd::MockImageCtx>::s_instance = nullptr;
+
+namespace util {
+
+template <> uint64_t get_file_offset(
+ MockImageCtx *image_ctx, uint64_t object_no, uint64_t offset) {
+ return Striper::get_file_offset(image_ctx->cct, &image_ctx->layout,
+ object_no, offset);
+}
+
+namespace {
+
+struct Mock {
+ static Mock* s_instance;
+
+ Mock() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD6(read_parent,
+ void(MockImageCtx*, uint64_t, io::ReadExtents*,
+ librados::snap_t, const ZTracer::Trace &, Context*));
+};
+
+Mock *Mock::s_instance = nullptr;
+
+} // anonymous namespace
+
+template <> void read_parent(
+ MockImageCtx *image_ctx, uint64_t object_no,
+ io::ReadExtents* extents, librados::snap_t snap_id,
+ const ZTracer::Trace &trace, Context* on_finish) {
+
+ Mock::s_instance->read_parent(image_ctx, object_no, extents, snap_id, trace,
+ on_finish);
+}
+
+} // namespace util
+} // namespace io
+
+} // namespace librbd
+
+#include "librbd/crypto/CryptoObjectDispatch.cc"
+
+namespace librbd {
+namespace crypto {
+
+using ::testing::_;
+using ::testing::ElementsAre;
+using ::testing::Invoke;
+using ::testing::InSequence;
+using ::testing::Pair;
+using ::testing::Return;
+using ::testing::WithArg;
+
+struct TestMockCryptoCryptoObjectDispatch : public TestMockFixture {
+ typedef CryptoObjectDispatch<librbd::MockImageCtx> MockCryptoObjectDispatch;
+ typedef io::AbstractObjectWriteRequest<librbd::MockImageCtx>
+ MockAbstractObjectWriteRequest;
+ typedef io::CopyupRequest<librbd::MockImageCtx> MockCopyupRequest;
+ typedef io::util::Mock MockUtils;
+
+ MockCryptoInterface* crypto;
+ MockImageCtx* mock_image_ctx;
+ MockCryptoObjectDispatch* mock_crypto_object_dispatch;
+
+ C_SaferCond finished_cond;
+ Context *on_finish = &finished_cond;
+ C_SaferCond dispatched_cond;
+ Context *on_dispatched = &dispatched_cond;
+ Context *dispatcher_ctx;
+ ceph::bufferlist data;
+ io::DispatchResult dispatch_result;
+ io::Extents extent_map;
+ int object_dispatch_flags = 0;
+ MockUtils mock_utils;
+ MockCopyupRequest copyup_request;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ crypto = new MockCryptoInterface();
+ mock_image_ctx = new MockImageCtx(*ictx);
+ mock_crypto_object_dispatch = new MockCryptoObjectDispatch(
+ mock_image_ctx, crypto);
+ data.append(std::string(4096, '1'));
+ }
+
+ void TearDown() override {
+ C_SaferCond cond;
+ Context *on_finish = &cond;
+ mock_crypto_object_dispatch->shut_down(on_finish);
+ ASSERT_EQ(0, cond.wait());
+
+ delete mock_crypto_object_dispatch;
+ delete mock_image_ctx;
+
+ TestMockFixture::TearDown();
+ }
+
+ void expect_object_read(io::ReadExtents* extents, uint64_t version = 0) {
+ EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_))
+ .WillOnce(Invoke([this, extents,
+ version](io::ObjectDispatchSpec* spec) {
+ auto* read = boost::get<io::ObjectDispatchSpec::ReadRequest>(
+ &spec->request);
+ ASSERT_TRUE(read != nullptr);
+
+ ASSERT_EQ(extents->size(), read->extents->size());
+ for (uint64_t i = 0; i < extents->size(); ++i) {
+ ASSERT_EQ((*extents)[i].offset, (*read->extents)[i].offset);
+ ASSERT_EQ((*extents)[i].length, (*read->extents)[i].length);
+ (*read->extents)[i].bl = (*extents)[i].bl;
+ (*read->extents)[i].extent_map = (*extents)[i].extent_map;
+ }
+
+ if (read->version != nullptr) {
+ *(read->version) = version;
+ }
+
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ dispatcher_ctx = &spec->dispatcher_ctx;
+ }));
+ }
+
+ void expect_read_parent(MockUtils &mock_utils, uint64_t object_no,
+ io::ReadExtents* extents, librados::snap_t snap_id,
+ int r) {
+ EXPECT_CALL(mock_utils,
+ read_parent(_, object_no, extents, snap_id, _, _))
+ .WillOnce(WithArg<5>(CompleteContext(
+ r, static_cast<asio::ContextWQ*>(nullptr))));
+ }
+
+ void expect_object_write(uint64_t object_off, const std::string& data,
+ int write_flags,
+ std::optional<uint64_t> assert_version) {
+ EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_))
+ .WillOnce(Invoke([this, object_off, data, write_flags,
+ assert_version](io::ObjectDispatchSpec* spec) {
+ auto* write = boost::get<io::ObjectDispatchSpec::WriteRequest>(
+ &spec->request);
+ ASSERT_TRUE(write != nullptr);
+
+ ASSERT_EQ(object_off, write->object_off);
+ ASSERT_TRUE(data == write->data.to_str());
+ ASSERT_EQ(write_flags, write->write_flags);
+ ASSERT_EQ(assert_version, write->assert_version);
+
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ dispatcher_ctx = &spec->dispatcher_ctx;
+ }));
+ }
+
+ void expect_object_write_same() {
+ EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_))
+ .WillOnce(Invoke([this](io::ObjectDispatchSpec* spec) {
+ auto* write_same = boost::get<
+ io::ObjectDispatchSpec::WriteSameRequest>(
+ &spec->request);
+ ASSERT_TRUE(write_same != nullptr);
+
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ dispatcher_ctx = &spec->dispatcher_ctx;
+ }));
+ }
+
+ void expect_get_object_size() {
+ EXPECT_CALL(*mock_image_ctx, get_object_size()).WillOnce(Return(
+ mock_image_ctx->layout.object_size));
+ }
+
+ void expect_remap_extents(uint64_t offset, uint64_t length) {
+ EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, remap_extents(
+ ElementsAre(Pair(offset, length)),
+ io::IMAGE_EXTENTS_MAP_TYPE_PHYSICAL_TO_LOGICAL));
+ }
+
+ void expect_get_parent_overlap(uint64_t overlap) {
+ EXPECT_CALL(*mock_image_ctx, get_parent_overlap(_, _))
+ .WillOnce(WithArg<1>(Invoke([overlap](uint64_t *o) {
+ *o = overlap;
+ return 0;
+ })));
+ }
+
+ void expect_prune_parent_extents(uint64_t object_overlap) {
+ EXPECT_CALL(*mock_image_ctx, prune_parent_extents(_, _))
+ .WillOnce(Return(object_overlap));
+ }
+
+ void expect_copyup(MockAbstractObjectWriteRequest** write_request, int r) {
+ EXPECT_CALL(copyup_request, append_request(_, _))
+ .WillOnce(WithArg<0>(
+ Invoke([write_request](
+ MockAbstractObjectWriteRequest *req) {
+ *write_request = req;
+ })));
+ EXPECT_CALL(copyup_request, send())
+ .WillOnce(Invoke([write_request, r]() {
+ (*write_request)->handle_copyup(r);
+ }));
+ }
+
+ void expect_encrypt(int count = 1) {
+ EXPECT_CALL(*crypto, encrypt(_, _)).Times(count);
+ }
+
+ void expect_decrypt(int count = 1) {
+ EXPECT_CALL(*crypto, decrypt(_, _)).Times(count);
+ }
+};
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, Flush) {
+ ASSERT_FALSE(mock_crypto_object_dispatch->flush(
+ io::FLUSH_SOURCE_USER, {}, nullptr, nullptr, &on_finish, nullptr));
+ ASSERT_EQ(on_finish, &finished_cond); // not modified
+ on_finish->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, Discard) {
+ expect_object_write_same();
+ ASSERT_TRUE(mock_crypto_object_dispatch->discard(
+ 0, 0, 4096, mock_image_ctx->get_data_io_context(), 0, {},
+ &object_dispatch_flags, nullptr, &dispatch_result, &on_finish,
+ on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+ dispatcher_ctx->complete(0);
+ ASSERT_EQ(0, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, AlignedReadFail) {
+ io::ReadExtents extents = {{0, 4096}};
+ expect_object_read(&extents);
+ ASSERT_TRUE(mock_crypto_object_dispatch->read(
+ 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {},
+ nullptr, &object_dispatch_flags, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+ dispatcher_ctx->complete(-EIO);
+ ASSERT_EQ(-EIO, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, AlignedRead) {
+ io::ReadExtents extents = {{0, 16384}, {32768, 4096}};
+ extents[0].bl.append(std::string(1024, '1') + std::string(1024, '2') +
+ std::string(1024, '3') + std::string(1024, '4'));
+ extents[0].extent_map = {{1024, 1024}, {3072, 2048}, {16384 - 1024, 1024}};
+ extents[1].bl.append(std::string(4096, '0'));
+ expect_object_read(&extents);
+ ASSERT_TRUE(mock_crypto_object_dispatch->read(
+ 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {},
+ nullptr, &object_dispatch_flags, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ expect_decrypt(3);
+ dispatcher_ctx->complete(0);
+ ASSERT_EQ(16384 + 4096, dispatched_cond.wait());
+
+ auto expected_bl_data = (
+ std::string(1024, '\0') + std::string(1024, '1') +
+ std::string(1024, '\0') + std::string(1024, '2') +
+ std::string(1024, '3') + std::string(3072, '\0') +
+ std::string(3072, '\0') + std::string(1024, '4'));
+ ASSERT_TRUE(extents[0].bl.to_str() == expected_bl_data);
+ ASSERT_THAT(extents[0].extent_map,
+ ElementsAre(Pair(0, 8192), Pair(16384 - 4096, 4096)));
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, ReadFromParent) {
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ expect_object_read(&extents);
+ expect_read_parent(mock_utils, 0, &extents, CEPH_NOSNAP, 8192);
+ ASSERT_TRUE(mock_crypto_object_dispatch->read(
+ 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {},
+ nullptr, &object_dispatch_flags, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ // no decrypt
+ dispatcher_ctx->complete(-ENOENT);
+ ASSERT_EQ(8192, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, ReadFromParentDisabled) {
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ expect_object_read(&extents);
+ ASSERT_TRUE(mock_crypto_object_dispatch->read(
+ 0, &extents, mock_image_ctx->get_data_io_context(), 0,
+ io::READ_FLAG_DISABLE_READ_FROM_PARENT, {},
+ nullptr, &object_dispatch_flags, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ // no decrypt
+ dispatcher_ctx->complete(-ENOENT);
+ ASSERT_EQ(-ENOENT, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedRead) {
+ io::ReadExtents extents = {{0, 1}, {8191, 1}, {8193, 1},
+ {16384 + 1, 4096 * 5 - 2}};
+ io::ReadExtents aligned_extents = {{0, 4096}, {4096, 4096}, {8192, 4096},
+ {16384, 4096 * 5}};
+ aligned_extents[0].bl.append(std::string("1") + std::string(4096, '0'));
+ aligned_extents[1].bl.append(std::string(4095, '0') + std::string("2"));
+ aligned_extents[2].bl.append(std::string("03") + std::string(4094, '0'));
+ aligned_extents[3].bl.append(std::string("0") + std::string(4095, '4') +
+ std::string(4096, '5') +
+ std::string(4095, '6') + std::string("0"));
+ aligned_extents[3].extent_map = {{16384, 4096}, {16384 + 2 * 4096, 4096},
+ {16384 + 4 * 4096, 4096}};
+
+ expect_object_read(&aligned_extents);
+ ASSERT_TRUE(mock_crypto_object_dispatch->read(
+ 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {},
+ nullptr, &object_dispatch_flags, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ dispatcher_ctx->complete(4096*8);
+ ASSERT_EQ(3 + 4096 * 5 - 2, dispatched_cond.wait());
+ ASSERT_TRUE(extents[0].bl.to_str() == std::string("1"));
+ ASSERT_TRUE(extents[1].bl.to_str() == std::string("2"));
+ ASSERT_TRUE(extents[2].bl.to_str() == std::string("3"));
+
+ auto expected_bl_data = (std::string(4095, '4') + std::string(4096, '5') +
+ std::string(4095, '6'));
+ ASSERT_TRUE(extents[3].bl.to_str() == expected_bl_data);
+ ASSERT_THAT(extents[3].extent_map,
+ ElementsAre(Pair(16384 + 1, 4095), Pair(16384 + 2 * 4096, 4096),
+ Pair(16384 + 4 * 4096, 4095)));
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, AlignedWrite) {
+ expect_encrypt();
+ ASSERT_TRUE(mock_crypto_object_dispatch->write(
+ 0, 0, std::move(data), mock_image_ctx->get_data_io_context(), 0, 0,
+ std::nullopt, {}, nullptr, nullptr, &dispatch_result, &on_finish,
+ on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_CONTINUE);
+ ASSERT_EQ(on_finish, &finished_cond); // not modified
+ on_finish->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWrite) {
+ ceph::bufferlist write_data;
+ uint64_t version = 1234;
+ write_data.append(std::string(8192, '1'));
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ extents[0].bl.append(std::string(4096, '2'));
+ extents[1].bl.append(std::string(4096, '3'));
+ expect_object_read(&extents, version);
+ ASSERT_TRUE(mock_crypto_object_dispatch->write(
+ 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(),
+ 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ auto expected_data =
+ std::string("2") + std::string(8192, '1') + std::string(4095, '3');
+ expect_object_write(0, expected_data, 0, std::make_optional(version));
+ dispatcher_ctx->complete(8192); // complete read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+ dispatcher_ctx->complete(0); // complete write
+ ASSERT_EQ(0, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteWithNoObject) {
+ ceph::bufferlist write_data;
+ write_data.append(std::string(8192, '1'));
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ expect_object_read(&extents);
+ ASSERT_TRUE(mock_crypto_object_dispatch->write(
+ 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(),
+ 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ expect_get_object_size();
+ expect_get_parent_overlap(0);
+ auto expected_data = (std::string(1, '\0') + std::string(8192, '1') +
+ std::string(4095, '\0'));
+ expect_object_write(0, expected_data, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE,
+ std::nullopt);
+ dispatcher_ctx->complete(-ENOENT); // complete read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+ dispatcher_ctx->complete(0); // complete write
+ ASSERT_EQ(0, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteFailCreate) {
+ ceph::bufferlist write_data;
+ write_data.append(std::string(8192, '1'));
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ expect_object_read(&extents);
+ ASSERT_TRUE(mock_crypto_object_dispatch->write(
+ 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(),
+ 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ expect_get_object_size();
+ expect_get_parent_overlap(0);
+ auto expected_data = (std::string(1, '\0') + std::string(8192, '1') +
+ std::string(4095, '\0'));
+ expect_object_write(0, expected_data, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE,
+ std::nullopt);
+ dispatcher_ctx->complete(-ENOENT); // complete read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ extents[0].bl.append(std::string(4096, '2'));
+ extents[1].bl.append(std::string(4096, '3'));
+ uint64_t version = 1234;
+ expect_object_read(&extents, version);
+ dispatcher_ctx->complete(-EEXIST); // complete write, request will restart
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ auto expected_data2 =
+ std::string("2") + std::string(8192, '1') + std::string(4095, '3');
+ expect_object_write(0, expected_data2, 0, std::make_optional(version));
+ dispatcher_ctx->complete(8192); // complete read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+ dispatcher_ctx->complete(0); // complete write
+ ASSERT_EQ(0, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteCopyup) {
+ MockObjectMap mock_object_map;
+ mock_image_ctx->object_map = &mock_object_map;
+ MockExclusiveLock mock_exclusive_lock;
+ mock_image_ctx->exclusive_lock = &mock_exclusive_lock;
+
+ ceph::bufferlist write_data;
+ write_data.append(std::string(8192, '1'));
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ expect_object_read(&extents);
+ ASSERT_TRUE(mock_crypto_object_dispatch->write(
+ 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(),
+ 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ expect_get_object_size();
+ expect_get_parent_overlap(mock_image_ctx->layout.object_size);
+ expect_remap_extents(0, mock_image_ctx->layout.object_size);
+ expect_prune_parent_extents(mock_image_ctx->layout.object_size);
+ EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillRepeatedly(
+ Return(true));
+ EXPECT_CALL(*mock_image_ctx->object_map, object_may_exist(0)).WillOnce(
+ Return(false));
+ MockAbstractObjectWriteRequest *write_request = nullptr;
+ expect_copyup(&write_request, 0);
+
+ // unaligned write restarted
+ uint64_t version = 1234;
+ extents[0].bl.append(std::string(4096, '2'));
+ extents[1].bl.append(std::string(4096, '3'));
+ expect_object_read(&extents, version);
+ dispatcher_ctx->complete(-ENOENT); // complete first read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ auto expected_data =
+ std::string("2") + std::string(8192, '1') + std::string(4095, '3');
+ expect_object_write(0, expected_data, 0, std::make_optional(version));
+ dispatcher_ctx->complete(8192); // complete second read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+ dispatcher_ctx->complete(0); // complete write
+ ASSERT_EQ(0, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteEmptyCopyup) {
+ MockObjectMap mock_object_map;
+ mock_image_ctx->object_map = &mock_object_map;
+ MockExclusiveLock mock_exclusive_lock;
+ mock_image_ctx->exclusive_lock = &mock_exclusive_lock;
+
+ ceph::bufferlist write_data;
+ write_data.append(std::string(8192, '1'));
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ expect_object_read(&extents);
+ ASSERT_TRUE(mock_crypto_object_dispatch->write(
+ 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(),
+ 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ expect_get_object_size();
+ expect_get_parent_overlap(mock_image_ctx->layout.object_size);
+ expect_remap_extents(0, mock_image_ctx->layout.object_size);
+ expect_prune_parent_extents(mock_image_ctx->layout.object_size);
+ EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillRepeatedly(
+ Return(true));
+ EXPECT_CALL(*mock_image_ctx->object_map, object_may_exist(0)).WillOnce(
+ Return(false));
+ MockAbstractObjectWriteRequest *write_request = nullptr;
+ expect_copyup(&write_request, 0);
+
+ // unaligned write restarted
+ expect_object_read(&extents);
+ dispatcher_ctx->complete(-ENOENT); // complete first read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ auto expected_data =
+ std::string(1, '\0') + std::string(8192, '1') +
+ std::string(4095, '\0');
+ expect_object_write(0, expected_data, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE,
+ std::nullopt);
+ dispatcher_ctx->complete(-ENOENT); // complete second read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+ dispatcher_ctx->complete(0); // complete write
+ ASSERT_EQ(0, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteFailVersionCheck) {
+ ceph::bufferlist write_data;
+ uint64_t version = 1234;
+ write_data.append(std::string(8192, '1'));
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ extents[0].bl.append(std::string(4096, '2'));
+ extents[1].bl.append(std::string(4096, '3'));
+ expect_object_read(&extents, version);
+ ASSERT_TRUE(mock_crypto_object_dispatch->write(
+ 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(),
+ 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ auto expected_data =
+ std::string("2") + std::string(8192, '1') + std::string(4095, '3');
+ expect_object_write(0, expected_data, 0, std::make_optional(version));
+ dispatcher_ctx->complete(8192); // complete read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ version = 1235;
+ expect_object_read(&extents, version);
+ extents[0].bl.append(std::string(4096, '2'));
+ extents[1].bl.append(std::string(4096, '3'));
+ dispatcher_ctx->complete(-ERANGE); // complete write, request will restart
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ expect_object_write(0, expected_data, 0, std::make_optional(version));
+ dispatcher_ctx->complete(8192); // complete read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+ dispatcher_ctx->complete(0); // complete write
+ ASSERT_EQ(0, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteWithAssertVersion) {
+ ceph::bufferlist write_data;
+ uint64_t version = 1234;
+ uint64_t assert_version = 1233;
+ write_data.append(std::string(8192, '1'));
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ extents[0].bl.append(std::string(4096, '2'));
+ extents[1].bl.append(std::string(4096, '3'));
+ expect_object_read(&extents, version);
+ ASSERT_TRUE(mock_crypto_object_dispatch->write(
+ 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(),
+ 0, 0, std::make_optional(assert_version), {}, nullptr, nullptr,
+ &dispatch_result, &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ dispatcher_ctx->complete(8192); // complete read
+ ASSERT_EQ(-ERANGE, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteWithExclusiveCreate) {
+ ceph::bufferlist write_data;
+ write_data.append(std::string(8192, '1'));
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ extents[0].bl.append(std::string(4096, '2'));
+ extents[1].bl.append(std::string(4096, '3'));
+ expect_object_read(&extents);
+ ASSERT_TRUE(mock_crypto_object_dispatch->write(
+ 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(),
+ 0, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, std::nullopt, {}, nullptr,
+ nullptr, &dispatch_result, &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ dispatcher_ctx->complete(8192); // complete read
+ ASSERT_EQ(-EEXIST, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, CompareAndWrite) {
+ ceph::bufferlist write_data;
+ uint64_t version = 1234;
+ write_data.append(std::string(8192, '1'));
+ ceph::bufferlist cmp_data;
+ cmp_data.append(std::string(4096, '2'));
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}, {0, 8192}};
+ extents[0].bl.append(std::string(4096, '2'));
+ extents[1].bl.append(std::string(4096, '3'));
+ extents[2].bl.append(std::string(8192, '2'));
+ expect_object_read(&extents, version);
+
+ ASSERT_TRUE(mock_crypto_object_dispatch->compare_and_write(
+ 0, 1, std::move(cmp_data), std::move(write_data),
+ mock_image_ctx->get_data_io_context(), 0, {}, nullptr, nullptr,
+ nullptr, &dispatch_result, &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ auto expected_data =
+ std::string("2") + std::string(8192, '1') + std::string(4095, '3');
+ expect_object_write(0, expected_data, 0, std::make_optional(version));
+ dispatcher_ctx->complete(4096*4); // complete read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+ dispatcher_ctx->complete(0); // complete write
+ ASSERT_EQ(0, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, CompareAndWriteFail) {
+ ceph::bufferlist write_data;
+ uint64_t version = 1234;
+ write_data.append(std::string(8192, '1'));
+ ceph::bufferlist cmp_data;
+ cmp_data.append(std::string(4094, '2') + std::string(2, '4'));
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}, {0, 8192}};
+ extents[0].bl.append(std::string(4096, '2'));
+ extents[1].bl.append(std::string(4096, '3'));
+ extents[2].bl.append(std::string(8192, '2'));
+ expect_object_read(&extents, version);
+
+ uint64_t mismatch_offset;
+ ASSERT_TRUE(mock_crypto_object_dispatch->compare_and_write(
+ 0, 1, std::move(cmp_data), std::move(write_data),
+ mock_image_ctx->get_data_io_context(), 0, {}, &mismatch_offset,
+ nullptr, nullptr, &dispatch_result, &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ dispatcher_ctx->complete(4096*4); // complete read
+ ASSERT_EQ(-EILSEQ, dispatched_cond.wait());
+ ASSERT_EQ(mismatch_offset, 4094);
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, WriteSame) {
+ io::LightweightBufferExtents buffer_extents;
+ ceph::bufferlist write_data;
+ write_data.append(std::string("12"));
+ expect_object_write(0, std::string("12121") , 0, std::nullopt);
+ ASSERT_TRUE(mock_crypto_object_dispatch->write_same(
+ 0, 0, 5, {{0, 5}}, std::move(write_data),
+ mock_image_ctx->get_data_io_context(), 0, {}, nullptr, nullptr,
+ &dispatch_result, &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+ dispatcher_ctx->complete(0);
+ ASSERT_EQ(0, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, PrepareCopyup) {
+ char* data = (char*)"0123456789";
+ io::SnapshotSparseBufferlist snapshot_sparse_bufferlist;
+ auto& snap1 = snapshot_sparse_bufferlist[0];
+ auto& snap2 = snapshot_sparse_bufferlist[1];
+
+ snap1.insert(0, 1, {io::SPARSE_EXTENT_STATE_DATA, 1,
+ ceph::bufferlist::static_from_mem(data + 1, 1)});
+ snap1.insert(8191, 1, {io::SPARSE_EXTENT_STATE_DATA, 1,
+ ceph::bufferlist::static_from_mem(data + 2, 1)});
+ snap1.insert(8193, 3, {io::SPARSE_EXTENT_STATE_DATA, 3,
+ ceph::bufferlist::static_from_mem(data + 3, 3)});
+
+ snap2.insert(0, 2, {io::SPARSE_EXTENT_STATE_ZEROED, 2});
+ snap2.insert(8191, 3, {io::SPARSE_EXTENT_STATE_DATA, 3,
+ ceph::bufferlist::static_from_mem(data + 6, 3)});
+ snap2.insert(16384, 1, {io::SPARSE_EXTENT_STATE_DATA, 1,
+ ceph::bufferlist::static_from_mem(data + 9, 1)});
+
+ expect_get_object_size();
+ expect_encrypt(6);
+ InSequence seq;
+ expect_remap_extents(0, 4096);
+ expect_remap_extents(4096, 4096);
+ expect_remap_extents(8192, 4096);
+ expect_remap_extents(0, 4096);
+ expect_remap_extents(4096, 8192);
+ expect_remap_extents(16384, 4096);
+ ASSERT_EQ(0, mock_crypto_object_dispatch->prepare_copyup(
+ 0, &snapshot_sparse_bufferlist));
+
+ ASSERT_EQ(2, snapshot_sparse_bufferlist.size());
+
+ auto& snap1_result = snapshot_sparse_bufferlist[0];
+ auto& snap2_result = snapshot_sparse_bufferlist[1];
+
+ auto it = snap1_result.begin();
+ ASSERT_NE(it, snap1_result.end());
+ ASSERT_EQ(0, it.get_off());
+ ASSERT_EQ(4096 * 3, it.get_len());
+
+ ASSERT_TRUE(it.get_val().bl.to_str() ==
+ std::string("1") + std::string(4095, '\0') +
+ std::string(4095, '\0') + std::string("2") +
+ std::string(1, '\0') + std::string("345") + std::string(4092, '\0'));
+ ASSERT_EQ(++it, snap1_result.end());
+
+ it = snap2_result.begin();
+ ASSERT_NE(it, snap2_result.end());
+ ASSERT_EQ(0, it.get_off());
+ ASSERT_EQ(4096 * 3, it.get_len());
+ ASSERT_TRUE(it.get_val().bl.to_str() ==
+ std::string(4096, '\0') +
+ std::string(4095, '\0') + std::string("6") +
+ std::string("7845") + std::string(4092, '\0'));
+
+ ASSERT_NE(++it, snap2_result.end());
+ ASSERT_EQ(16384, it.get_off());
+ ASSERT_EQ(4096, it.get_len());
+ ASSERT_TRUE(it.get_val().bl.to_str() ==
+ std::string("9") + std::string(4095, '\0'));
+ ASSERT_EQ(++it, snap2_result.end());
+}
+
+} // namespace io
+} // namespace librbd
diff --git a/src/test/librbd/crypto/test_mock_FormatRequest.cc b/src/test/librbd/crypto/test_mock_FormatRequest.cc
new file mode 100644
index 000000000..2008deb68
--- /dev/null
+++ b/src/test/librbd/crypto/test_mock_FormatRequest.cc
@@ -0,0 +1,195 @@
+// -*- 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/crypto/MockCryptoInterface.h"
+#include "test/librbd/mock/crypto/MockEncryptionFormat.h"
+#include "librbd/crypto/Utils.h"
+
+namespace librbd {
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+#include "librbd/crypto/FormatRequest.cc"
+
+namespace librbd {
+namespace crypto {
+
+namespace util {
+
+template <> void set_crypto(
+ MockImageCtx *image_ctx, ceph::ref_t<CryptoInterface> crypto) {
+ image_ctx->crypto = crypto.get();
+}
+
+} // namespace util
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::WithArg;
+
+template <>
+struct ShutDownCryptoRequest<MockImageCtx> {
+ Context *on_finish = nullptr;
+ static ShutDownCryptoRequest *s_instance;
+ static ShutDownCryptoRequest *create(
+ MockImageCtx* image_ctx, Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ ShutDownCryptoRequest() {
+ s_instance = this;
+ }
+};
+
+ShutDownCryptoRequest<MockImageCtx> *ShutDownCryptoRequest<
+ MockImageCtx>::s_instance = nullptr;
+
+struct TestMockCryptoFormatRequest : public TestMockFixture {
+ typedef FormatRequest<librbd::MockImageCtx> MockFormatRequest;
+ typedef ShutDownCryptoRequest<MockImageCtx> MockShutDownCryptoRequest;
+
+ MockImageCtx* mock_image_ctx;
+ C_SaferCond finished_cond;
+ Context *on_finish = &finished_cond;
+ MockEncryptionFormat* mock_encryption_format;
+ Context* format_context;
+ MockCryptoInterface* crypto;
+ MockCryptoInterface* old_crypto;
+ MockFormatRequest* mock_format_request;
+ std::string key = std::string(64, '0');
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ mock_image_ctx = new MockImageCtx(*ictx);
+ mock_encryption_format = new MockEncryptionFormat();
+ crypto = new MockCryptoInterface();
+ old_crypto = new MockCryptoInterface();
+ mock_image_ctx->crypto = old_crypto;
+ mock_format_request = MockFormatRequest::create(
+ mock_image_ctx,
+ std::unique_ptr<MockEncryptionFormat>(mock_encryption_format),
+ on_finish);
+ }
+
+ void TearDown() override {
+ crypto->put();
+ old_crypto->put();
+ delete mock_image_ctx;
+ TestMockFixture::TearDown();
+ }
+
+ void expect_test_journal_feature(bool has_journal=false) {
+ EXPECT_CALL(*mock_image_ctx, test_features(
+ RBD_FEATURE_JOURNALING)).WillOnce(Return(has_journal));
+ }
+
+ void expect_encryption_format() {
+ EXPECT_CALL(*mock_encryption_format, format(
+ mock_image_ctx, _)).WillOnce(
+ WithArg<1>(Invoke([this](Context* ctx) {
+ format_context = ctx;
+ })));
+ }
+
+ void expect_image_flush(int r) {
+ EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_)).WillOnce(
+ Invoke([r](io::ImageDispatchSpec* spec) {
+ ASSERT_TRUE(boost::get<io::ImageDispatchSpec::Flush>(
+ &spec->request) != nullptr);
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ spec->aio_comp->set_request_count(1);
+ spec->aio_comp->add_request();
+ spec->aio_comp->complete_request(r);
+ }));
+ }
+};
+
+TEST_F(TestMockCryptoFormatRequest, JournalEnabled) {
+ expect_test_journal_feature(true);
+ mock_format_request->send();
+ ASSERT_EQ(-ENOTSUP, finished_cond.wait());
+ ASSERT_EQ(old_crypto, mock_image_ctx->crypto);
+}
+
+TEST_F(TestMockCryptoFormatRequest, FailShutDownCrypto) {
+ expect_test_journal_feature(false);
+ MockShutDownCryptoRequest mock_shutdown_crypto_request;
+ EXPECT_CALL(mock_shutdown_crypto_request, send());
+ mock_format_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ mock_shutdown_crypto_request.on_finish->complete(-EIO);
+ ASSERT_EQ(-EIO, finished_cond.wait());
+ ASSERT_EQ(old_crypto, mock_image_ctx->crypto);
+}
+
+TEST_F(TestMockCryptoFormatRequest, FormatFail) {
+ mock_image_ctx->crypto = nullptr;
+ expect_test_journal_feature(false);
+ expect_encryption_format();
+ mock_format_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ format_context->complete(-EIO);
+ ASSERT_EQ(-EIO, finished_cond.wait());
+ ASSERT_EQ(nullptr, mock_image_ctx->crypto);
+}
+
+TEST_F(TestMockCryptoFormatRequest, Success) {
+ mock_image_ctx->crypto = nullptr;
+ expect_test_journal_feature(false);
+ expect_encryption_format();
+ mock_format_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_flush(0);
+ EXPECT_CALL(*mock_encryption_format, get_crypto()).WillOnce(Return(crypto));
+ format_context->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(crypto, mock_image_ctx->crypto);
+}
+
+TEST_F(TestMockCryptoFormatRequest, FailFlush) {
+ mock_image_ctx->crypto = nullptr;
+ expect_test_journal_feature(false);
+ expect_encryption_format();
+ mock_format_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_flush(-EIO);
+ format_context->complete(0);
+ ASSERT_EQ(-EIO, finished_cond.wait());
+ ASSERT_EQ(nullptr, mock_image_ctx->crypto);
+}
+
+TEST_F(TestMockCryptoFormatRequest, CryptoAlreadyLoaded) {
+ expect_test_journal_feature(false);
+ MockShutDownCryptoRequest mock_shutdown_crypto_request;
+ EXPECT_CALL(mock_shutdown_crypto_request, send());
+ mock_format_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_encryption_format();
+ mock_shutdown_crypto_request.on_finish->complete(0);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_flush(0);
+ EXPECT_CALL(*mock_encryption_format, get_crypto()).WillOnce(Return(crypto));
+ format_context->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(crypto, mock_image_ctx->crypto);
+}
+
+} // namespace crypto
+} // namespace librbd
diff --git a/src/test/librbd/crypto/test_mock_LoadRequest.cc b/src/test/librbd/crypto/test_mock_LoadRequest.cc
new file mode 100644
index 000000000..f87d4eea2
--- /dev/null
+++ b/src/test/librbd/crypto/test_mock_LoadRequest.cc
@@ -0,0 +1,125 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/crypto/CryptoObjectDispatch.h"
+#include "librbd/crypto/Utils.h"
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/crypto/MockCryptoInterface.h"
+#include "test/librbd/mock/crypto/MockEncryptionFormat.h"
+#include "test/librbd/mock/io/MockObjectDispatch.h"
+
+namespace librbd {
+namespace crypto {
+
+template <>
+struct CryptoObjectDispatch<MockImageCtx> : public io::MockObjectDispatch {
+
+ static CryptoObjectDispatch* create(
+ MockImageCtx* image_ctx,ceph::ref_t<CryptoInterface> crypto) {
+ return new CryptoObjectDispatch();
+ }
+
+ CryptoObjectDispatch() {
+ }
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#include "librbd/crypto/LoadRequest.cc"
+
+namespace librbd {
+namespace crypto {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::WithArgs;
+
+struct TestMockCryptoLoadRequest : public TestMockFixture {
+ typedef LoadRequest<librbd::MockImageCtx> MockLoadRequest;
+
+ MockImageCtx* mock_image_ctx;
+ C_SaferCond finished_cond;
+ Context *on_finish = &finished_cond;
+ MockEncryptionFormat* mock_encryption_format;
+ MockCryptoInterface* crypto;
+ Context* load_context;
+ MockLoadRequest* mock_load_request;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ mock_image_ctx = new MockImageCtx(*ictx);
+ mock_encryption_format = new MockEncryptionFormat();
+ crypto = new MockCryptoInterface();
+ mock_load_request = MockLoadRequest::create(
+ mock_image_ctx,
+ std::unique_ptr<MockEncryptionFormat>(mock_encryption_format),
+ on_finish);
+ }
+
+ void TearDown() override {
+ crypto->put();
+ delete mock_image_ctx;
+ TestMockFixture::TearDown();
+ }
+
+ void expect_test_journal_feature() {
+ expect_test_journal_feature(mock_image_ctx, false);
+ }
+
+ void expect_test_journal_feature(MockImageCtx* ctx, bool has_journal=false) {
+ EXPECT_CALL(*ctx, test_features(
+ RBD_FEATURE_JOURNALING)).WillOnce(Return(has_journal));
+ }
+
+ void expect_encryption_load() {
+ EXPECT_CALL(*mock_encryption_format, load(
+ mock_image_ctx, _)).WillOnce(
+ WithArgs<1>(Invoke([this](Context* ctx) {
+ load_context = ctx;
+ })));
+ }
+
+};
+
+TEST_F(TestMockCryptoLoadRequest, CryptoAlreadyLoaded) {
+ mock_image_ctx->crypto = crypto;
+ mock_load_request->send();
+ ASSERT_EQ(-EEXIST, finished_cond.wait());
+}
+
+TEST_F(TestMockCryptoLoadRequest, JournalEnabled) {
+ expect_test_journal_feature(mock_image_ctx, true);
+ mock_load_request->send();
+ ASSERT_EQ(-ENOTSUP, finished_cond.wait());
+}
+
+TEST_F(TestMockCryptoLoadRequest, LoadFail) {
+ expect_test_journal_feature();
+ expect_encryption_load();
+ mock_load_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ load_context->complete(-EIO);
+ ASSERT_EQ(-EIO, finished_cond.wait());
+}
+
+TEST_F(TestMockCryptoLoadRequest, Success) {
+ mock_image_ctx->parent = nullptr;
+ expect_test_journal_feature(mock_image_ctx, false);
+ expect_encryption_load();
+ mock_load_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ EXPECT_CALL(*mock_encryption_format, get_crypto()).WillOnce(Return(crypto));
+ load_context->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(crypto, mock_image_ctx->crypto);
+}
+
+} // namespace crypto
+} // namespace librbd
diff --git a/src/test/librbd/crypto/test_mock_ShutDownCryptoRequest.cc b/src/test/librbd/crypto/test_mock_ShutDownCryptoRequest.cc
new file mode 100644
index 000000000..7585ba292
--- /dev/null
+++ b/src/test/librbd/crypto/test_mock_ShutDownCryptoRequest.cc
@@ -0,0 +1,144 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/crypto/Utils.h"
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/crypto/MockCryptoInterface.h"
+
+#include "librbd/crypto/ShutDownCryptoRequest.cc"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace crypto {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::WithArgs;
+
+struct TestMockShutDownCryptoRequest : public TestMockFixture {
+ typedef ShutDownCryptoRequest<MockTestImageCtx> MockShutDownCryptoRequest;
+
+ MockTestImageCtx* mock_image_ctx;
+ C_SaferCond finished_cond;
+ Context *on_finish = &finished_cond;
+ MockShutDownCryptoRequest* mock_shutdown_crypto_request;
+ MockCryptoInterface* crypto;
+ Context* shutdown_object_dispatch_context;
+ Context* shutdown_image_dispatch_context;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ mock_image_ctx = new MockTestImageCtx(*ictx);
+ crypto = new MockCryptoInterface();
+ mock_image_ctx->crypto = crypto;
+ mock_shutdown_crypto_request = MockShutDownCryptoRequest::create(
+ mock_image_ctx, on_finish);
+ }
+
+ void TearDown() override {
+ crypto->put();
+ delete mock_image_ctx;
+ TestMockFixture::TearDown();
+ }
+
+ void expect_crypto_object_layer_exists_check(bool exists) {
+ EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, exists(
+ io::OBJECT_DISPATCH_LAYER_CRYPTO)).WillOnce(Return(exists));
+ }
+
+ void expect_crypto_image_layer_exists_check(bool exists) {
+ EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, exists(
+ io::IMAGE_DISPATCH_LAYER_CRYPTO)).WillOnce(Return(exists));
+ }
+
+ void expect_shutdown_crypto_object_dispatch() {
+ EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, shut_down_dispatch(
+ io::OBJECT_DISPATCH_LAYER_CRYPTO, _)).WillOnce(
+ WithArgs<1>(Invoke([this](Context* ctx) {
+ shutdown_object_dispatch_context = ctx;
+ })));
+ }
+
+ void expect_shutdown_crypto_image_dispatch() {
+ EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, shut_down_dispatch(
+ io::IMAGE_DISPATCH_LAYER_CRYPTO, _)).WillOnce(
+ WithArgs<1>(Invoke([this](Context* ctx) {
+ shutdown_image_dispatch_context = ctx;
+ })));
+ }
+};
+
+TEST_F(TestMockShutDownCryptoRequest, NoCryptoObjectDispatch) {
+ expect_crypto_object_layer_exists_check(false);
+ mock_shutdown_crypto_request->send();
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(nullptr, mock_image_ctx->crypto);
+}
+
+TEST_F(TestMockShutDownCryptoRequest, FailShutdownObjectDispatch) {
+ expect_crypto_object_layer_exists_check(true);
+ expect_shutdown_crypto_object_dispatch();
+ mock_shutdown_crypto_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ shutdown_object_dispatch_context->complete(-EIO);
+ ASSERT_EQ(-EIO, finished_cond.wait());
+ ASSERT_NE(nullptr, mock_image_ctx->crypto);
+}
+
+TEST_F(TestMockShutDownCryptoRequest, NoCryptoImageDispatch) {
+ expect_crypto_object_layer_exists_check(true);
+ expect_shutdown_crypto_object_dispatch();
+ mock_shutdown_crypto_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_crypto_image_layer_exists_check(false);
+ shutdown_object_dispatch_context->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(nullptr, mock_image_ctx->crypto);
+}
+
+TEST_F(TestMockShutDownCryptoRequest, FailShutdownImageDispatch) {
+ expect_crypto_object_layer_exists_check(true);
+ expect_shutdown_crypto_object_dispatch();
+ mock_shutdown_crypto_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_crypto_image_layer_exists_check(true);
+ expect_shutdown_crypto_image_dispatch();
+ shutdown_object_dispatch_context->complete(0);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ shutdown_image_dispatch_context->complete(-EIO);
+ ASSERT_EQ(-EIO, finished_cond.wait());
+ ASSERT_NE(nullptr, mock_image_ctx->crypto);
+}
+
+TEST_F(TestMockShutDownCryptoRequest, Success) {
+ expect_crypto_object_layer_exists_check(true);
+ expect_shutdown_crypto_object_dispatch();
+ mock_shutdown_crypto_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_crypto_image_layer_exists_check(true);
+ expect_shutdown_crypto_image_dispatch();
+ shutdown_object_dispatch_context->complete(0);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ shutdown_image_dispatch_context->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(nullptr, mock_image_ctx->crypto);
+}
+
+} // namespace crypto
+} // namespace librbd
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 000000000..c56b681b9
--- /dev/null
+++ b/src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc
@@ -0,0 +1,805 @@
+// -*- 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/AsioEngine.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/internal.h"
+#include "librbd/Operations.h"
+#include "librbd/deep_copy/Handler.h"
+#include "librbd/deep_copy/ImageCopyRequest.h"
+#include "librbd/deep_copy/ObjectCopyRequest.h"
+#include "librbd/object_map/DiffRequest.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, uint32_t flags, Handler* handler,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ std::lock_guard locker{s_instance->lock};
+ s_instance->snap_map = &snap_map;
+ s_instance->flags = flags;
+ s_instance->object_contexts[object_number] = on_finish;
+ s_instance->cond.notify_all();
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ ceph::mutex lock = ceph::make_mutex("lock");
+ ceph::condition_variable cond;
+
+ const SnapMap *snap_map = nullptr;
+ std::map<uint64_t, Context *> object_contexts;
+ uint32_t flags = 0;
+
+ ObjectCopyRequest() {
+ s_instance = this;
+ }
+};
+
+ObjectCopyRequest<librbd::MockTestImageCtx>* ObjectCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace deep_copy
+
+namespace object_map {
+
+template <>
+struct DiffRequest<MockTestImageCtx> {
+ BitVector<2>* object_diff_state = nullptr;
+ Context* on_finish = nullptr;
+ static DiffRequest* s_instance;
+ static DiffRequest* create(MockTestImageCtx *image_ctx,
+ uint64_t snap_id_start, uint64_t snap_id_end,
+ BitVector<2>* object_diff_state,
+ Context* on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->object_diff_state = object_diff_state;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ DiffRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+DiffRequest<MockTestImageCtx>* DiffRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace object_map
+} // 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::Invoke;
+using ::testing::Return;
+
+class TestMockDeepCopyImageCopyRequest : public TestMockFixture {
+public:
+ typedef ImageCopyRequest<librbd::MockTestImageCtx> MockImageCopyRequest;
+ typedef ObjectCopyRequest<librbd::MockTestImageCtx> MockObjectCopyRequest;
+ typedef object_map::DiffRequest<librbd::MockTestImageCtx> MockDiffRequest;
+
+ librbd::ImageCtx *m_src_image_ctx;
+ librbd::ImageCtx *m_dst_image_ctx;
+
+ std::shared_ptr<librbd::AsioEngine> m_asio_engine;
+ asio::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));
+
+ m_asio_engine = std::make_shared<librbd::AsioEngine>(
+ m_src_image_ctx->md_ctx);
+ m_work_queue = m_asio_engine->get_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_diff_send(MockDiffRequest& mock_request,
+ const BitVector<2>& diff_state, int r) {
+ EXPECT_CALL(mock_request, send())
+ .WillOnce(Invoke([this, &mock_request, diff_state, r]() {
+ if (r >= 0) {
+ *mock_request.object_diff_state = diff_state;
+ }
+ m_work_queue->queue(mock_request.on_finish, r);
+ }));
+ }
+
+ void expect_object_copy_send(MockObjectCopyRequest &mock_object_copy_request,
+ uint32_t flags) {
+ EXPECT_CALL(mock_object_copy_request, send())
+ .WillOnce(Invoke([&mock_object_copy_request, flags]() {
+ ASSERT_EQ(flags, mock_object_copy_request.flags);
+ }));
+ }
+
+ bool complete_object_copy(MockObjectCopyRequest &mock_object_copy_request,
+ uint64_t object_num, Context **object_ctx, int r) {
+ std::unique_lock locker{mock_object_copy_request.lock};
+ while (mock_object_copy_request.object_contexts.count(object_num) == 0) {
+ if (mock_object_copy_request.cond.wait_for(locker, 10s) ==
+ std::cv_status::timeout) {
+ 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) {
+ std::unique_lock locker{mock_object_copy_request.lock};
+ while (mock_object_copy_request.snap_map == nullptr) {
+ if (mock_object_copy_request.cond.wait_for(locker, 10s) ==
+ std::cv_status::timeout) {
+ 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) {
+ NoOpProgressContext prog_ctx;
+ int r = image_ctx->operations->snap_create(
+ cls::rbd::UserSnapshotNamespace(), snap_name, 0, prog_ctx);
+ 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;
+ MockDiffRequest mock_diff_request;
+ expect_diff_send(mock_diff_request, {}, -EINVAL);
+ 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, 0);
+
+ librbd::deep_copy::NoOpHandler 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, FastDiffNonExistent) {
+ 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);
+
+ InSequence seq;
+
+ MockDiffRequest mock_diff_request;
+ BitVector<2> diff_state;
+ diff_state.resize(1);
+ expect_diff_send(mock_diff_request, diff_state, 0);
+
+ expect_get_image_size(mock_src_image_ctx, 1 << m_src_image_ctx->order);
+ expect_get_image_size(mock_src_image_ctx, 0);
+ expect_op_work_queue(mock_src_image_ctx);
+
+ librbd::deep_copy::NoOpHandler 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(0, ctx.wait());
+}
+
+TEST_F(TestMockDeepCopyImageCopyRequest, FastDiffExistsDirty) {
+ 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);
+
+ InSequence seq;
+
+ MockDiffRequest mock_diff_request;
+ BitVector<2> diff_state;
+ diff_state.resize(1);
+ diff_state[0] = object_map::DIFF_STATE_DATA_UPDATED;
+ expect_diff_send(mock_diff_request, diff_state, 0);
+
+ expect_get_image_size(mock_src_image_ctx, 1 << m_src_image_ctx->order);
+ expect_get_image_size(mock_src_image_ctx, 0);
+ MockObjectCopyRequest mock_object_copy_request;
+ expect_object_copy_send(mock_object_copy_request, 0);
+
+ librbd::deep_copy::NoOpHandler 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, FastDiffExistsClean) {
+ 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);
+
+ InSequence seq;
+
+ MockDiffRequest mock_diff_request;
+ BitVector<2> diff_state;
+ diff_state.resize(1);
+ diff_state[0] = object_map::DIFF_STATE_DATA;
+ expect_diff_send(mock_diff_request, diff_state, 0);
+
+ expect_get_image_size(mock_src_image_ctx, 1 << m_src_image_ctx->order);
+ expect_get_image_size(mock_src_image_ctx, 0);
+ MockObjectCopyRequest mock_object_copy_request;
+ expect_object_copy_send(mock_object_copy_request,
+ OBJECT_COPY_REQUEST_FLAG_EXISTS_CLEAN);
+
+ librbd::deep_copy::NoOpHandler 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, FastDiffMix) {
+ librados::snap_t snap_id_end;
+ ASSERT_EQ(0, create_snap("copy", &snap_id_end));
+
+ uint64_t object_count = 12;
+
+ 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;
+
+ MockDiffRequest mock_diff_request;
+ BitVector<2> diff_state;
+ diff_state.resize(object_count);
+ diff_state[1] = object_map::DIFF_STATE_DATA_UPDATED;
+ diff_state[2] = object_map::DIFF_STATE_DATA_UPDATED;
+ diff_state[3] = object_map::DIFF_STATE_DATA;
+ diff_state[5] = object_map::DIFF_STATE_DATA_UPDATED;
+ diff_state[8] = object_map::DIFF_STATE_DATA;
+ diff_state[9] = object_map::DIFF_STATE_DATA;
+ diff_state[10] = object_map::DIFF_STATE_DATA_UPDATED;
+ expect_diff_send(mock_diff_request, diff_state, 0);
+
+ 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_op_work_queue(mock_src_image_ctx);
+ expect_object_copy_send(mock_object_copy_request, 0);
+ expect_object_copy_send(mock_object_copy_request, 0);
+ expect_object_copy_send(mock_object_copy_request,
+ OBJECT_COPY_REQUEST_FLAG_EXISTS_CLEAN);
+ expect_op_work_queue(mock_src_image_ctx);
+ expect_object_copy_send(mock_object_copy_request, 0);
+ expect_op_work_queue(mock_src_image_ctx);
+ expect_object_copy_send(mock_object_copy_request,
+ OBJECT_COPY_REQUEST_FLAG_EXISTS_CLEAN);
+ expect_object_copy_send(mock_object_copy_request,
+ OBJECT_COPY_REQUEST_FLAG_EXISTS_CLEAN);
+ expect_object_copy_send(mock_object_copy_request, 0);
+ expect_op_work_queue(mock_src_image_ctx);
+
+ std::vector<bool> seen(object_count);
+ struct Handler : public librbd::deep_copy::NoOpHandler {
+ Handler(std::vector<bool>* seen) : m_seen(seen) {}
+
+ int update_progress(uint64_t object_no, uint64_t end_object_no) override {
+ EXPECT_THAT(object_no, ::testing::AllOf(::testing::Ge(1),
+ ::testing::Le(m_seen->size())));
+ EXPECT_EQ(end_object_no, m_seen->size());
+ EXPECT_FALSE((*m_seen)[object_no - 1]);
+ (*m_seen)[object_no - 1] = true;
+ return 0;
+ }
+
+ std::vector<bool>* m_seen;
+ } handler(&seen);
+
+ 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, &handler, &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, 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, nullptr, 0));
+ ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 5, nullptr, 0));
+ ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 8, nullptr, 0));
+ ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 9, nullptr, 0));
+ ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 10, nullptr, 0));
+ ASSERT_EQ(0, ctx.wait());
+
+ EXPECT_THAT(seen, ::testing::Each(::testing::IsTrue()));
+}
+
+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;
+
+ MockDiffRequest mock_diff_request;
+ expect_diff_send(mock_diff_request, {}, -EINVAL);
+ 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 Handler : public librbd::deep_copy::NoOpHandler {
+ public:
+ uint64_t object_count;
+ librbd::deep_copy::ObjectNumber expected_object_number;
+
+ Handler(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;
+ }
+ } handler(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, &handler, &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;
+ MockDiffRequest mock_diff_request;
+ expect_diff_send(mock_diff_request, {}, -EINVAL);
+ 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, 0);
+
+ librbd::deep_copy::NoOpHandler 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;
+ MockDiffRequest mock_diff_request;
+ expect_diff_send(mock_diff_request, {}, -EINVAL);
+ 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, 0);
+
+ librbd::deep_copy::NoOpHandler 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;
+ MockDiffRequest mock_diff_request;
+ expect_diff_send(mock_diff_request, {}, -EINVAL);
+ 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, 0);
+
+ librbd::deep_copy::NoOpHandler 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;
+ MockDiffRequest mock_diff_request;
+ expect_diff_send(mock_diff_request, {}, -EINVAL);
+ 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 Handler : public librbd::deep_copy::NoOpHandler {
+ 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;
+ }
+ } handler;
+
+ 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, &handler, &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, handler.object_number.get());
+}
+
+TEST_F(TestMockDeepCopyImageCopyRequest, CancelBeforeSend) {
+ 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);
+
+ InSequence seq;
+
+ MockDiffRequest mock_diff_request;
+ expect_diff_send(mock_diff_request, {}, -EINVAL);
+ expect_get_image_size(mock_src_image_ctx, 2 * (1 << m_src_image_ctx->order));
+ expect_get_image_size(mock_src_image_ctx, 0);
+
+ librbd::deep_copy::NoOpHandler 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->cancel();
+ request->send();
+
+ ASSERT_EQ(-ECANCELED, ctx.wait());
+}
+
+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::deep_copy::NoOpHandler 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::deep_copy::NoOpHandler 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::deep_copy::NoOpHandler 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::deep_copy::NoOpHandler 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 000000000..ba59e3cdb
--- /dev/null
+++ b/src/test/librbd/deep_copy/test_mock_MetadataCopyRequest.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 "include/rbd/librbd.hpp"
+#include "include/stringify.h"
+#include "librbd/AsioEngine.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/deep_copy/MetadataCopyRequest.h"
+#include "librbd/image/GetMetadataRequest.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 image {
+
+template <>
+struct GetMetadataRequest<MockTestImageCtx> {
+ std::map<std::string, bufferlist>* pairs = nullptr;
+ Context* on_finish = nullptr;
+
+ static GetMetadataRequest* s_instance;
+ static GetMetadataRequest* create(librados::IoCtx&,
+ const std::string& oid,
+ bool filter_internal,
+ const std::string& filter_key_prefix,
+ const std::string& last_key,
+ uint32_t max_results,
+ std::map<std::string, bufferlist>* pairs,
+ Context* on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->pairs = pairs;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ GetMetadataRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+GetMetadataRequest<MockTestImageCtx>* GetMetadataRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namspace image
+} // 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::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockDeepCopyMetadataCopyRequest : public TestMockFixture {
+public:
+ typedef MetadataCopyRequest<librbd::MockTestImageCtx> MockMetadataCopyRequest;
+ typedef image::GetMetadataRequest<MockTestImageCtx> MockGetMetadataRequest;
+ typedef std::map<std::string, bufferlist> Metadata;
+
+ librbd::ImageCtx *m_src_image_ctx;
+ librbd::ImageCtx *m_dst_image_ctx;
+
+ std::shared_ptr<librbd::AsioEngine> m_asio_engine;
+ asio::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));
+
+ m_asio_engine = std::make_shared<librbd::AsioEngine>(
+ m_src_image_ctx->md_ctx);
+ m_work_queue = m_asio_engine->get_work_queue();
+ }
+
+ void expect_get_metadata(MockGetMetadataRequest& mock_request,
+ const Metadata& metadata, int r) {
+ EXPECT_CALL(mock_request, send())
+ .WillOnce(Invoke([this, &mock_request, metadata, r]() {
+ *mock_request.pairs = metadata;
+ m_work_queue->queue(mock_request.on_finish, 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;
+ MockGetMetadataRequest mock_request;
+ expect_get_metadata(mock_request, key_values_1, 0);
+ expect_metadata_set(mock_dst_image_ctx, key_values_1, 0);
+ expect_get_metadata(mock_request, 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;
+ MockGetMetadataRequest mock_request;
+ expect_get_metadata(mock_request, 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;
+ MockGetMetadataRequest mock_request;
+ expect_get_metadata(mock_request, 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;
+ MockGetMetadataRequest mock_request;
+ expect_get_metadata(mock_request, 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 000000000..d8bb0678a
--- /dev/null
+++ b/src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc
@@ -0,0 +1,1103 @@
+// -*- 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/neorados/RADOS.hpp"
+#include "include/rbd/librbd.hpp"
+#include "include/rbd/object_map_types.h"
+#include "librbd/AsioEngine.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/internal.h"
+#include "librbd/Operations.h"
+#include "librbd/api/Image.h"
+#include "librbd/api/Io.h"
+#include "librbd/deep_copy/ObjectCopyRequest.h"
+#include "librbd/deep_copy/Utils.h"
+#include "librbd/io/ReadResult.h"
+#include "librbd/io/Utils.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 {
+namespace util {
+
+template <> void file_to_extents(
+ MockTestImageCtx* image_ctx, uint64_t offset, uint64_t length,
+ uint64_t buffer_offset,
+ striper::LightweightObjectExtents* object_extents) {
+ Striper::file_to_extents(image_ctx->cct, &image_ctx->layout, offset, length,
+ 0, buffer_offset, object_extents);
+}
+
+template <> void extent_to_file(
+ MockTestImageCtx* image_ctx, uint64_t object_no, uint64_t offset,
+ uint64_t length,
+ std::vector<std::pair<uint64_t, uint64_t> >& extents) {
+ Striper::extent_to_file(image_ctx->cct, &image_ctx->layout, object_no,
+ offset, length, extents);
+}
+
+} // namespace util
+} // 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 = api::Io<>::write(*image_ctx, 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
+
+
+MATCHER(IsListSnaps, "") {
+ auto req = boost::get<io::ImageDispatchSpec::ListSnaps>(&arg->request);
+ return (req != nullptr);
+}
+
+MATCHER_P2(IsRead, snap_id, image_interval, "") {
+ auto req = boost::get<io::ImageDispatchSpec::Read>(&arg->request);
+ if (req == nullptr ||
+ arg->io_context->read_snap().value_or(CEPH_NOSNAP) != snap_id) {
+ return false;
+ }
+
+ // ensure the read request encloses the full snapshot delta
+ interval_set<uint64_t> expected_interval(image_interval);
+ interval_set<uint64_t> read_interval;
+ for (auto &image_extent : arg->image_extents) {
+ read_interval.insert(image_extent.first, image_extent.second);
+ }
+
+ interval_set<uint64_t> intersection;
+ intersection.intersection_of(expected_interval, read_interval);
+ expected_interval.subtract(intersection);
+ return expected_interval.empty();
+}
+
+class TestMockDeepCopyObjectCopyRequest : public TestMockFixture {
+public:
+ typedef ObjectCopyRequest<librbd::MockTestImageCtx> MockObjectCopyRequest;
+
+ librbd::ImageCtx *m_src_image_ctx;
+ librbd::ImageCtx *m_dst_image_ctx;
+
+ std::shared_ptr<librbd::AsioEngine> m_asio_engine;
+ asio::ContextWQ *m_work_queue;
+
+ SnapMap m_snap_map;
+ SnapSeqs m_snap_seqs;
+ 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));
+
+ m_asio_engine = std::make_shared<librbd::AsioEngine>(
+ m_src_image_ctx->md_ctx);
+ m_work_queue = m_asio_engine->get_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(Return(new LambdaContext([](int){})));
+ }
+
+ void expect_list_snaps(librbd::MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, send(IsListSnaps()))
+ .WillOnce(Invoke(
+ [&mock_image_ctx, r](io::ImageDispatchSpec* spec) {
+ if (r < 0) {
+ spec->fail(r);
+ return;
+ }
+
+ spec->image_dispatcher =
+ mock_image_ctx.image_ctx->io_image_dispatcher;
+ mock_image_ctx.image_ctx->io_image_dispatcher->send(spec);
+ }));
+ }
+
+ 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 src_snap_id_end,
+ librados::snap_t dst_snap_id_start,
+ uint32_t flags, Context *on_finish) {
+ SnapMap snap_map;
+ util::compute_snap_map(mock_dst_image_ctx.cct, src_snap_id_start,
+ src_snap_id_end, m_dst_snap_ids, m_snap_seqs,
+ &snap_map);
+
+ 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,
+ snap_map, 0, flags, nullptr, on_finish);
+ }
+
+ void expect_read(librbd::MockTestImageCtx& mock_image_ctx,
+ uint64_t snap_id, uint64_t offset, uint64_t length, int r) {
+ interval_set<uint64_t> extents;
+ extents.insert(offset, length);
+ expect_read(mock_image_ctx, snap_id, extents, r);
+ }
+
+ void expect_read(librbd::MockTestImageCtx& mock_image_ctx, uint64_t snap_id,
+ const interval_set<uint64_t> &extents, int r) {
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher,
+ send(IsRead(snap_id, extents)))
+ .WillOnce(Invoke(
+ [&mock_image_ctx, r](io::ImageDispatchSpec* spec) {
+ if (r < 0) {
+ spec->fail(r);
+ return;
+ }
+
+ spec->image_dispatcher =
+ mock_image_ctx.image_ctx->io_image_dispatcher;
+ mock_image_ctx.image_ctx->io_image_dispatcher->send(spec);
+ }));
+ }
+
+ 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(ceph_mutex_is_locked(mock_image_ctx.image_ctx->image_lock));
+ mock_image_ctx.image_ctx->object_map->aio_update<Context>(
+ snap_id, 0, 1, state, boost::none, {}, false, ctx);
+ })),
+ Return(true)));
+ }
+ }
+ }
+
+ void expect_prepare_copyup(MockTestImageCtx& mock_image_ctx, int r = 0) {
+ EXPECT_CALL(*mock_image_ctx.io_object_dispatcher,
+ prepare_copyup(_, _)).WillOnce(Return(r));
+ }
+
+ int create_snap(librbd::ImageCtx *image_ctx, const char* snap_name,
+ librados::snap_t *snap_id) {
+ NoOpProgressContext prog_ctx;
+ int r = image_ctx->operations->snap_create(
+ cls::rbd::UserSnapshotNamespace(), snap_name, 0, prog_ctx);
+ 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_snap_seqs[src_snap_id] = dst_snap_id;
+ 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 = api::Io<>::read(*m_src_image_ctx, 0, object_size,
+ librbd::io::ReadResult{&bl}, 0);
+ if (r < 0) {
+ return r;
+ }
+
+ r = api::Io<>::write(*m_dst_image_ctx, 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 = api::Io<>::read(
+ *m_src_image_ctx, 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 = api::Io<>::read(
+ *m_dst_image_ctx, 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_op_work_queue(mock_src_image_ctx);
+ 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,
+ CEPH_NOSNAP, 0, 0, &ctx);
+
+ InSequence seq;
+ expect_list_snaps(mock_src_image_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_op_work_queue(mock_src_image_ctx);
+ 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,
+ CEPH_NOSNAP, 0, 0, &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, 0);
+ expect_read(mock_src_image_ctx, m_src_snap_ids[0], 0, one.range_end(), 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_prepare_copyup(mock_dst_image_ctx);
+ expect_start_op(mock_exclusive_lock);
+ expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, 0);
+
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_EQ(0, compare_objects());
+}
+
+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_op_work_queue(mock_src_image_ctx);
+ 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,
+ CEPH_NOSNAP, 0, 0, &ctx);
+
+ InSequence seq;
+ expect_list_snaps(mock_src_image_ctx, 0);
+ expect_read(mock_src_image_ctx, m_src_snap_ids[0], 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_op_work_queue(mock_src_image_ctx);
+ 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,
+ CEPH_NOSNAP, 0, 0, &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, 0);
+ expect_read(mock_src_image_ctx, m_src_snap_ids[0], 0, one.range_end(), 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_prepare_copyup(mock_dst_image_ctx);
+ 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_op_work_queue(mock_src_image_ctx);
+ 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,
+ CEPH_NOSNAP, 0, 0, &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, 0);
+ expect_read(mock_src_image_ctx, m_src_snap_ids[0], 0, one.range_end(), 0);
+ expect_read(mock_src_image_ctx, m_src_snap_ids[2], two, 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);
+ expect_prepare_copyup(mock_dst_image_ctx);
+ 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);
+
+ 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, api::Io<>::discard(
+ *m_src_image_ctx, 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_op_work_queue(mock_src_image_ctx);
+ 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,
+ CEPH_NOSNAP, 0, 0, &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, 0);
+ expect_read(mock_src_image_ctx, m_src_snap_ids[0], 0, one.range_end(), 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_prepare_copyup(mock_dst_image_ctx);
+ 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);
+
+ 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, api::Io<>::discard(
+ *m_src_image_ctx, 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_op_work_queue(mock_src_image_ctx);
+ 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,
+ CEPH_NOSNAP, 0, 0, &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, 0);
+ expect_read(mock_src_image_ctx, m_src_snap_ids[1], 0, one.range_end(), 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);
+
+ expect_prepare_copyup(mock_dst_image_ctx);
+ 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);
+
+ 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_op_work_queue(mock_src_image_ctx);
+ 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,
+ CEPH_NOSNAP, 0, 0, &ctx);
+
+ InSequence seq;
+ expect_list_snaps(mock_src_image_ctx, 0);
+ expect_read(mock_src_image_ctx, m_src_snap_ids[0], 0, one.range_end(), 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, -EBLOCKLISTED);
+
+ request->send();
+ ASSERT_EQ(-EBLOCKLISTED, ctx.wait());
+}
+
+TEST_F(TestMockDeepCopyObjectCopyRequest, PrepareCopyupError) {
+ // 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_op_work_queue(mock_src_image_ctx);
+ 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,
+ CEPH_NOSNAP, 0, 0, &ctx);
+
+ InSequence seq;
+ expect_list_snaps(mock_src_image_ctx, 0);
+ expect_read(mock_src_image_ctx, m_src_snap_ids[0], 0, one.range_end(), 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_prepare_copyup(mock_dst_image_ctx, -EIO);
+
+ request->send();
+ ASSERT_EQ(-EIO, 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_seqs.rbegin()->second = CEPH_NOSNAP;
+ m_dst_snap_ids.pop_back();
+
+ 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_op_work_queue(mock_src_image_ctx);
+ 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,
+ CEPH_NOSNAP,
+ dst_snap_id_start,
+ 0, &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, 0);
+
+ expect_read(mock_src_image_ctx, m_src_snap_ids[1], two, 0);
+ expect_read(mock_src_image_ctx, m_src_snap_ids[2], three, 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);
+
+ expect_prepare_copyup(mock_dst_image_ctx);
+ 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);
+
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_EQ(0, compare_objects());
+}
+
+TEST_F(TestMockDeepCopyObjectCopyRequest, Incremental) {
+ 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_op_work_queue(mock_src_image_ctx);
+ expect_test_features(mock_dst_image_ctx);
+ expect_get_object_count(mock_dst_image_ctx);
+
+ // scribble some data
+ interval_set<uint64_t> one;
+ scribble(m_src_image_ctx, 10, 102400, &one);
+ ASSERT_EQ(0, create_snap("snap1"));
+ mock_dst_image_ctx.snaps = m_dst_image_ctx->snaps;
+
+ InSequence seq;
+
+ C_SaferCond ctx1;
+ auto request1 = create_request(mock_src_image_ctx, mock_dst_image_ctx,
+ 0, m_src_snap_ids[0], 0, 0, &ctx1);
+
+ expect_list_snaps(mock_src_image_ctx, 0);
+
+ expect_read(mock_src_image_ctx, m_src_snap_ids[0], 0, one.range_end(), 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);
+
+ librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx(
+ request1->get_dst_io_ctx()));
+ expect_prepare_copyup(mock_dst_image_ctx);
+ expect_start_op(mock_exclusive_lock);
+ expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, 0);
+
+ request1->send();
+ ASSERT_EQ(0, ctx1.wait());
+
+ // clean (no-updates) snapshots
+ ASSERT_EQ(0, create_snap("snap2"));
+ ASSERT_EQ(0, create_snap("snap3"));
+ mock_dst_image_ctx.snaps = m_dst_image_ctx->snaps;
+
+ C_SaferCond ctx2;
+ auto request2 = create_request(mock_src_image_ctx, mock_dst_image_ctx,
+ m_src_snap_ids[0], m_src_snap_ids[2],
+ m_dst_snap_ids[0], 0, &ctx2);
+
+ expect_list_snaps(mock_src_image_ctx, 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);
+ 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);
+
+ request2->send();
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(0, compare_objects());
+}
+
+TEST_F(TestMockDeepCopyObjectCopyRequest, SkipSnapList) {
+ 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_op_work_queue(mock_src_image_ctx);
+ expect_test_features(mock_dst_image_ctx);
+ expect_get_object_count(mock_dst_image_ctx);
+
+ ASSERT_EQ(0, create_snap("snap1"));
+ mock_dst_image_ctx.snaps = m_dst_image_ctx->snaps;
+
+ InSequence seq;
+
+ // clean (no-updates) snapshots
+ ASSERT_EQ(0, create_snap("snap2"));
+ mock_dst_image_ctx.snaps = m_dst_image_ctx->snaps;
+
+ C_SaferCond ctx;
+ auto request = create_request(mock_src_image_ctx, mock_dst_image_ctx,
+ m_src_snap_ids[0], m_src_snap_ids[1],
+ m_dst_snap_ids[0],
+ OBJECT_COPY_REQUEST_FLAG_EXISTS_CLEAN, &ctx);
+
+ 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());
+}
+
+} // 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 000000000..209339973
--- /dev/null
+++ b/src/test/librbd/deep_copy/test_mock_SetHeadRequest.cc
@@ -0,0 +1,296 @@
+// -*- 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/AsioEngine.h"
+#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;
+
+ std::shared_ptr<librbd::AsioEngine> m_asio_engine;
+ asio::ContextWQ *m_work_queue;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+
+ m_asio_engine = std::make_shared<librbd::AsioEngine>(
+ m_image_ctx->md_ctx);
+ m_work_queue = m_asio_engine->get_work_queue();
+ }
+
+ void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) {
+ EXPECT_CALL(mock_exclusive_lock, start_op(_)).WillOnce(Return(new LambdaContext([](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 000000000..6a5289822
--- /dev/null
+++ b/src/test/librbd/deep_copy/test_mock_SnapshotCopyRequest.cc
@@ -0,0 +1,923 @@
+// -*- 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/AsioEngine.h"
+#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;
+
+ std::shared_ptr<librbd::AsioEngine> m_asio_engine;
+ asio::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));
+
+ m_asio_engine = std::make_shared<librbd::AsioEngine>(
+ m_src_image_ctx->md_ctx);
+ m_work_queue = m_asio_engine->get_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(Return(new LambdaContext([](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) {
+ NoOpProgressContext prog_ctx;
+ int r = image_ctx->operations->snap_create(snap_ns, snap_name.c_str(), 0,
+ prog_ctx);
+ 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::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY,
+ {"peer uuid1"}, "", CEPH_NOSNAP}},
+ "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 000000000..af24f5a0c
--- /dev/null
+++ b/src/test/librbd/deep_copy/test_mock_SnapshotCreateRequest.cc
@@ -0,0 +1,262 @@
+// -*- 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/AsioEngine.h"
+#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;
+
+ std::shared_ptr<librbd::AsioEngine> m_asio_engine;
+ asio::ContextWQ *m_work_queue;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+
+ m_asio_engine = std::make_shared<librbd::AsioEngine>(
+ m_image_ctx->md_ctx);
+ m_work_queue = m_asio_engine->get_work_queue();
+ }
+
+ void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) {
+ EXPECT_CALL(mock_exclusive_lock, start_op(_)).WillOnce(Return(new LambdaContext([](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) {
+ uint64_t flags = SNAP_CREATE_FLAG_SKIP_OBJECT_MAP |
+ SNAP_CREATE_FLAG_SKIP_NOTIFY_QUIESCE;
+ EXPECT_CALL(*mock_image_ctx.operations,
+ execute_snap_create(_, StrEq(snap_name), _, 0, flags, _))
+ .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 000000000..943b8cc2d
--- /dev/null
+++ b/src/test/librbd/exclusive_lock/test_mock_PostAcquireRequest.cc
@@ -0,0 +1,582 @@
+// -*- 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/cache/MockImageCache.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,
+ ceph::shared_mutex &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());
+ }
+
+ void expect_acquired_exclusive_lock(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.plugin_registry, acquired_exclusive_lock(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+
+ void expect_prerelease_exclusive_lock(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.plugin_registry, prerelease_exclusive_lock(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+
+};
+
+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.image_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);
+
+ expect_acquired_exclusive_lock(mock_image_ctx, 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.image_lock, false);
+ expect_handle_prepare_lock_complete(mock_image_ctx);
+
+ expect_acquired_exclusive_lock(mock_image_ctx, 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, 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.image_lock, false);
+ expect_handle_prepare_lock_complete(mock_image_ctx);
+
+ expect_acquired_exclusive_lock(mock_image_ctx, 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, 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.image_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);
+
+ expect_acquired_exclusive_lock(mock_image_ctx, 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.image_lock, false);
+ expect_handle_prepare_lock_complete(mock_image_ctx);
+
+ expect_acquired_exclusive_lock(mock_image_ctx, 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, 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;
+ 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.image_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;
+ 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.image_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, InitImageCacheError) {
+ 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.image_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);
+
+ expect_acquired_exclusive_lock(mock_image_ctx, -ENOENT);
+ expect_prerelease_exclusive_lock(mock_image_ctx, 0);
+
+ 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(-ENOENT, 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;
+ 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;
+ 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.image_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);
+
+ expect_acquired_exclusive_lock(mock_image_ctx, 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 000000000..5b4bce6dd
--- /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 000000000..466a3ab42
--- /dev/null
+++ b/src/test/librbd/exclusive_lock/test_mock_PreReleaseRequest.cc
@@ -0,0 +1,388 @@
+// -*- 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/cache/MockImageCache.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/ImageDispatch.h"
+#include "librbd/exclusive_lock/PreReleaseRequest.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <list>
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace exclusive_lock {
+
+template <>
+struct ImageDispatch<MockTestImageCtx> {
+ MOCK_METHOD3(set_require_lock, void(bool init_shutdown, io::Direction,
+ Context*));
+ MOCK_METHOD1(unset_require_lock, void(io::Direction));
+};
+
+} // namespace exclusive_lock
+
+namespace util {
+
+inline ImageCtx* get_image_ctx(MockTestImageCtx* image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+// template definitions
+#include "librbd/exclusive_lock/PreReleaseRequest.cc"
+
+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 ImageDispatch<MockTestImageCtx> MockImageDispatch;
+ typedef PreReleaseRequest<MockTestImageCtx> MockPreReleaseRequest;
+
+ void expect_complete_context(MockContext &mock_context, int r) {
+ EXPECT_CALL(mock_context, complete(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_set_require_lock(MockImageDispatch &mock_image_dispatch,
+ bool init_shutdown,
+ librbd::io::Direction direction, int r) {
+ EXPECT_CALL(mock_image_dispatch, set_require_lock(init_shutdown,
+ direction, _))
+ .WillOnce(WithArg<2>(Invoke([r](Context* ctx) { ctx->complete(r); })));
+ }
+
+ void expect_set_require_lock(MockTestImageCtx &mock_image_ctx,
+ MockImageDispatch &mock_image_dispatch,
+ bool init_shutdown, int r) {
+ expect_test_features(mock_image_ctx, RBD_FEATURE_EXCLUSIVE_LOCK, true);
+ if (!mock_image_ctx.clone_copy_on_read) {
+ expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING,
+ ((mock_image_ctx.features & RBD_FEATURE_JOURNALING) != 0));
+ if ((mock_image_ctx.features & RBD_FEATURE_JOURNALING) == 0) {
+ expect_test_features(mock_image_ctx, RBD_FEATURE_DIRTY_CACHE,
+ ((mock_image_ctx.features & RBD_FEATURE_DIRTY_CACHE) != 0));
+ }
+ }
+ if (mock_image_ctx.clone_copy_on_read ||
+ (mock_image_ctx.features & RBD_FEATURE_JOURNALING) != 0 ||
+ (mock_image_ctx.features & RBD_FEATURE_DIRTY_CACHE) != 0) {
+ expect_set_require_lock(mock_image_dispatch, init_shutdown,
+ librbd::io::DIRECTION_BOTH, r);
+ } else {
+ expect_set_require_lock(mock_image_dispatch, init_shutdown,
+ librbd::io::DIRECTION_WRITE, r);
+ }
+ }
+
+ void expect_unset_require_lock(MockImageDispatch &mock_image_dispatch) {
+ EXPECT_CALL(mock_image_dispatch, unset_require_lock(
+ io::DIRECTION_BOTH));
+ }
+
+ void expect_cancel_op_requests(MockTestImageCtx &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(MockTestImageCtx &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(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_prerelease_exclusive_lock(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.plugin_registry, prerelease_exclusive_lock(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+
+ void expect_invalidate_cache(MockTestImageCtx &mock_image_ctx,
+ int r) {
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, invalidate_cache(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+
+ 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());
+ }
+
+ void expect_flush_io(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, send(_))
+ .WillOnce(Invoke([&mock_image_ctx, r](io::ImageDispatchSpec* spec) {
+ ASSERT_TRUE(boost::get<io::ImageDispatchSpec::Flush>(
+ &spec->request) != nullptr);
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto aio_comp = spec->aio_comp;
+ auto ctx = new LambdaContext([aio_comp](int r) {
+ if (r < 0) {
+ aio_comp->fail(r);
+ } else {
+ aio_comp->set_request_count(1);
+ aio_comp->add_request();
+ aio_comp->complete_request(r);
+ }
+ });
+ mock_image_ctx.image_ctx->op_work_queue->queue(ctx, r);
+ }));
+ }
+
+ 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));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ expect_cancel_op_requests(mock_image_ctx, 0);
+ MockImageDispatch mock_image_dispatch;
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, false, 0);
+
+ expect_prepare_lock(mock_image_ctx);
+
+ expect_prerelease_exclusive_lock(mock_image_ctx, 0);
+
+ expect_invalidate_cache(mock_image_ctx, 0);
+
+ expect_flush_io(mock_image_ctx, 0);
+
+ expect_flush_notifies(mock_image_ctx);
+
+ MockJournal mock_journal;
+ mock_image_ctx.journal = &mock_journal;
+ expect_close_journal(mock_image_ctx, mock_journal, -EINVAL);
+
+ MockObjectMap mock_object_map;
+ 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, &mock_image_dispatch, 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));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ MockImageDispatch mock_image_dispatch;
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, false, 0);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+ expect_cancel_op_requests(mock_image_ctx, 0);
+ expect_prepare_lock(mock_image_ctx);
+
+ expect_prerelease_exclusive_lock(mock_image_ctx, 0);
+
+ expect_invalidate_cache(mock_image_ctx, 0);
+
+ expect_flush_io(mock_image_ctx, 0);
+
+ expect_flush_notifies(mock_image_ctx);
+
+ MockObjectMap mock_object_map;
+ 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, &mock_image_dispatch, 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));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ MockImageDispatch mock_image_dispatch;
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, true, 0);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+ expect_cancel_op_requests(mock_image_ctx, 0);
+
+ expect_prerelease_exclusive_lock(mock_image_ctx, 0);
+
+ expect_invalidate_cache(mock_image_ctx, 0);
+
+ expect_flush_io(mock_image_ctx, 0);
+
+ expect_flush_notifies(mock_image_ctx);
+
+ C_SaferCond release_ctx;
+ C_SaferCond ctx;
+ MockPreReleaseRequest *req = MockPreReleaseRequest::create(
+ mock_image_ctx, &mock_image_dispatch, true, m_async_op_tracker, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockExclusiveLockPreReleaseRequest, Blocklisted) {
+ 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_cancel_op_requests(mock_image_ctx, 0);
+ MockImageDispatch mock_image_dispatch;
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, false,
+ -EBLOCKLISTED);
+ expect_prepare_lock(mock_image_ctx);
+
+ expect_prerelease_exclusive_lock(mock_image_ctx, 0);
+
+ expect_invalidate_cache(mock_image_ctx, -EBLOCKLISTED);
+
+ expect_flush_io(mock_image_ctx, -EBLOCKLISTED);
+
+ expect_flush_notifies(mock_image_ctx);
+
+ MockJournal mock_journal;
+ mock_image_ctx.journal = &mock_journal;
+ expect_close_journal(mock_image_ctx, mock_journal, -EBLOCKLISTED);
+
+ MockObjectMap mock_object_map;
+ 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, &mock_image_dispatch, false, m_async_op_tracker, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockExclusiveLockPreReleaseRequest, Disabled) {
+ 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_cancel_op_requests(mock_image_ctx, 0);
+ MockImageDispatch mock_image_dispatch;
+
+ expect_test_features(mock_image_ctx, RBD_FEATURE_EXCLUSIVE_LOCK, false);
+
+ expect_prepare_lock(mock_image_ctx);
+
+ expect_prerelease_exclusive_lock(mock_image_ctx, 0);
+
+ expect_invalidate_cache(mock_image_ctx, 0);
+
+ expect_flush_io(mock_image_ctx, 0);
+
+ expect_flush_notifies(mock_image_ctx);
+
+ MockJournal mock_journal;
+ mock_image_ctx.journal = &mock_journal;
+ expect_close_journal(mock_image_ctx, mock_journal, -EINVAL);
+
+ MockObjectMap mock_object_map;
+ 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, &mock_image_dispatch, false, 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 000000000..b8be4708e
--- /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>
+#ifndef _WIN32
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#endif
+#if defined(__linux__)
+#include <linux/fs.h>
+#endif
+#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 get_page_size()
+#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 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, {},
+ nullptr);
+ 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, {},
+ nullptr);
+ 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, {},
+ nullptr);
+ 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, "", {},
+ nullptr);
+
+ 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)
+{
+ 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 (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);
+}
+
+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);
+}
+
+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("--io-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);
+}
+
+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);
+}
+
+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 - 1);
+ 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 = PAGE_SIZE;
+ 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':
+ #ifdef O_DIRECT
+ o_direct = O_DIRECT;
+ #endif
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 2)
+ usage();
+ pool = argv[0];
+ iname = argv[1];
+
+ #ifndef _WIN32
+ 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);
+ #endif
+
+ 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 000000000..66d594eb0
--- /dev/null
+++ b/src/test/librbd/image/test_mock_AttachChildRequest.cc
@@ -0,0 +1,275 @@
+// -*- 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 "librbd/internal.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));
+ NoOpProgressContext prog_ctx;
+ ASSERT_EQ(0, image_ctx->operations->snap_create(
+ cls::rbd::UserSnapshotNamespace{}, "snap", 0, prog_ctx));
+ 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 000000000..de5f64431
--- /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 000000000..25de66dab
--- /dev/null
+++ b/src/test/librbd/image/test_mock_CloneRequest.cc
@@ -0,0 +1,960 @@
+// -*- 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/deep_copy/MetadataCopyRequest.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 deep_copy {
+
+template <>
+struct MetadataCopyRequest<MockTestImageCtx> {
+ Context* on_finish = nullptr;
+
+ static MetadataCopyRequest* s_instance;
+ static MetadataCopyRequest* create(MockTestImageCtx* src_image_ctx,
+ 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());
+};
+
+MetadataCopyRequest<MockTestImageCtx>* MetadataCopyRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace deep_copy
+
+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,
+ bool skip_mirror_enable,
+ cls::rbd::MirrorImageMode mode,
+ const std::string &non_primary_global_image_id,
+ const std::string &primary_mirror_uuid,
+ asio::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,
+ asio::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(MockTestImageCtx* image_ctx,
+ cls::rbd::MirrorImageMode mode,
+ const std::string &non_primary_global_image_id,
+ bool image_clean, Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ EXPECT_TRUE(image_clean);
+ 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 deep_copy::MetadataCopyRequest<MockTestImageCtx> MockMetadataCopyRequest;
+ 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));
+ NoOpProgressContext prog_ctx;
+ ASSERT_EQ(0, image_ctx->operations->snap_create(
+ cls::rbd::UserSnapshotNamespace{}, "snap", 0, prog_ctx));
+ 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);
+ })));
+ }
+
+ 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_copy(MockMetadataCopyRequest& 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_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);
+ }));
+ }
+
+ 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);
+
+ MockMetadataCopyRequest mock_request;
+ expect_metadata_copy(mock_request, 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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);
+
+ MockMetadataCopyRequest mock_request;
+ expect_metadata_copy(mock_request, 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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);
+
+ MockMetadataCopyRequest mock_request;
+ expect_metadata_copy(mock_request, 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ image_ctx->op_work_queue, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageCloneRequest, MetadataCopyError) {
+ 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);
+
+ MockMetadataCopyRequest mock_request;
+ expect_metadata_copy(mock_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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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);
+
+ MockMetadataCopyRequest mock_request;
+ expect_metadata_copy(mock_request, 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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);
+
+ MockMetadataCopyRequest mock_request;
+ expect_metadata_copy(mock_request, 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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);
+
+ MockMetadataCopyRequest mock_request;
+ expect_metadata_copy(mock_request, 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ image_ctx->op_work_queue, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageCloneRequest, SnapshotMirrorEnableNonPrimary) {
+ 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);
+
+ MockMetadataCopyRequest mock_request;
+ expect_metadata_copy(mock_request, 0);
+
+ MockMirrorEnableRequest mock_mirror_enable_request;
+ expect_mirror_enable(mock_mirror_enable_request, 0);
+
+ 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,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ "global image id", "primary mirror uuid",
+ image_ctx->op_work_queue, &ctx);
+ req->send();
+ ASSERT_EQ(0, 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 000000000..6812125cb
--- /dev/null
+++ b/src/test/librbd/image/test_mock_DetachChildRequest.cc
@@ -0,0 +1,454 @@
+// -*- 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/TypeTraits.h"
+#include "librbd/image/DetachChildRequest.h"
+#include "librbd/trash/RemoveRequest.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 image {
+
+template <>
+struct TypeTraits<MockTestImageCtx> {
+ typedef librbd::MockContextWQ ContextWQ;
+};
+
+} // namespace image
+
+namespace trash {
+
+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,
+ MockTestImageCtx *image_ctx,
+ ContextWQ *op_work_queue, bool force,
+ ProgressContext &prog_ctx, 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 trash
+} // 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;
+ typedef trash::RemoveRequest<MockTestImageCtx> MockTrashRemoveRequest;
+
+ 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, &mock_image_ctx, r](Context* ctx) {
+ EXPECT_EQ(0U, mock_image_ctx.read_only_mask &
+ IMAGE_READ_ONLY_FLAG_NON_PRIMARY);
+ 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);
+ }));
+ }
+
+ 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);
+ })));
+ }
+
+ void expect_trash_get(MockImageCtx &mock_image_ctx,
+ librados::MockTestMemIoCtxImpl &mock_io_ctx_impl,
+ const cls::rbd::TrashImageSpec& trash_spec,
+ int r) {
+ using ceph::encode;
+ EXPECT_CALL(mock_io_ctx_impl,
+ exec(RBD_TRASH, _, StrEq("rbd"),
+ StrEq("trash_get"), _, _, _, _))
+ .WillOnce(WithArg<5>(Invoke([trash_spec, r](bufferlist* bl) {
+ encode(trash_spec, *bl);
+ return r;
+ })));
+ }
+
+ void expect_trash_remove(MockTrashRemoveRequest& mock_trash_remove_request,
+ int r) {
+ EXPECT_CALL(mock_trash_remove_request, send())
+ .WillOnce(Invoke([&mock_trash_remove_request, r]() {
+ mock_trash_remove_request.on_finish->complete(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);
+ const cls::rbd::TrashImageSpec trash_spec;
+ expect_trash_get(mock_image_ctx, *mock_io_ctx_impl, trash_spec, -ENOENT);
+ 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, ParentAutoRemove) {
+ 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);
+ const cls::rbd::TrashImageSpec trash_spec =
+ {cls::rbd::TRASH_IMAGE_SOURCE_USER_PARENT, "parent", {}, {}};
+
+ expect_trash_get(mock_image_ctx, *mock_io_ctx_impl, trash_spec, 0);
+ MockTrashRemoveRequest mock_trash_remove_request;
+ expect_trash_remove(mock_trash_remove_request, 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);
+
+ 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 000000000..4d9f012f8
--- /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 000000000..d90fc4ab0
--- /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 000000000..faae4f201
--- /dev/null
+++ b/src/test/librbd/image/test_mock_PreRemoveRequest.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/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/exclusive_lock/StandardPolicy.cc"
+#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_exclusive_lock_policy(MockTestImageCtx& mock_image_ctx) {
+ if (m_mock_imctx->exclusive_lock != nullptr) {
+ EXPECT_CALL(mock_image_ctx, set_exclusive_lock_policy(_))
+ .WillOnce(Invoke([](exclusive_lock::Policy* policy) {
+ ASSERT_FALSE(policy->may_auto_request_lock());
+ delete policy;
+ }));
+ }
+ }
+
+ 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_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, 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_exclusive_lock_policy(*m_mock_imctx);
+ expect_set_journal_policy(*m_mock_imctx);
+ expect_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_exclusive_lock_policy(*m_mock_imctx);
+ expect_set_journal_policy(*m_mock_imctx);
+ expect_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_exclusive_lock_policy(*m_mock_imctx);
+ expect_set_journal_policy(*m_mock_imctx);
+ expect_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;
+ 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_exclusive_lock_policy(*m_mock_imctx);
+ expect_set_journal_policy(*m_mock_imctx);
+ expect_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;
+ 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_exclusive_lock_policy(*m_mock_imctx);
+ expect_set_journal_policy(*m_mock_imctx);
+ expect_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_exclusive_lock_policy(*m_mock_imctx);
+ expect_set_journal_policy(*m_mock_imctx);
+ expect_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_exclusive_lock_policy(*m_mock_imctx);
+ expect_set_journal_policy(*m_mock_imctx);
+ expect_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_exclusive_lock_policy(*m_mock_imctx);
+ expect_set_journal_policy(*m_mock_imctx);
+ expect_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 000000000..e60409615
--- /dev/null
+++ b/src/test/librbd/image/test_mock_RefreshRequest.cc
@@ -0,0 +1,1757 @@
+// -*- 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/GetMetadataRequest.h"
+#include "librbd/image/RefreshRequest.h"
+#include "librbd/image/RefreshParentRequest.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <arpa/inet.h>
+#include <queue>
+#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 GetMetadataRequest<MockRefreshImageCtx> {
+ std::string oid;
+ std::map<std::string, bufferlist>* pairs = nullptr;
+ Context* on_finish = nullptr;
+
+ static GetMetadataRequest* s_instance;
+ static GetMetadataRequest* create(librados::IoCtx&,
+ const std::string& oid,
+ bool filter_internal,
+ const std::string& filter_key_prefix,
+ const std::string& last_key,
+ uint32_t max_results,
+ std::map<std::string, bufferlist>* pairs,
+ Context* on_finish) {
+ ceph_assert(s_instance != nullptr);
+ EXPECT_EQ("conf_", filter_key_prefix);
+ EXPECT_EQ("conf_", last_key);
+ s_instance->oid = oid;
+ s_instance->pairs = pairs;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ GetMetadataRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+template <>
+struct RefreshParentRequest<MockRefreshImageCtx> {
+ static std::queue<RefreshParentRequest*> s_instances;
+ static RefreshParentRequest* create(MockRefreshImageCtx &mock_image_ctx,
+ const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info,
+ Context *on_finish) {
+ ceph_assert(!s_instances.empty());
+ auto instance = s_instances.front();
+ instance->on_finish = on_finish;
+ return instance;
+ }
+ static bool is_refresh_required(MockRefreshImageCtx &mock_image_ctx,
+ const ParentImageInfo& parent_md,
+ const MigrationInfo &migration_info) {
+ ceph_assert(!s_instances.empty());
+ return s_instances.front()->is_refresh_required();
+ }
+
+ Context *on_finish = nullptr;
+
+ RefreshParentRequest() {
+ s_instances.push(this);
+ }
+
+ ~RefreshParentRequest() {
+ ceph_assert(this == s_instances.front());
+ s_instances.pop();
+ }
+
+ MOCK_CONST_METHOD0(is_refresh_required, bool());
+ MOCK_METHOD0(send, void());
+ MOCK_METHOD0(apply, void());
+ MOCK_METHOD1(finalize, void(Context *));
+};
+
+GetMetadataRequest<MockRefreshImageCtx>* GetMetadataRequest<MockRefreshImageCtx>::s_instance = nullptr;
+std::queue<RefreshParentRequest<MockRefreshImageCtx>*> RefreshParentRequest<MockRefreshImageCtx>::s_instances;
+
+} // namespace image
+
+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 GetMetadataRequest<MockRefreshImageCtx> MockGetMetadataRequest;
+ typedef RefreshRequest<MockRefreshImageCtx> MockRefreshRequest;
+ typedef RefreshParentRequest<MockRefreshImageCtx> MockRefreshParentRequest;
+ typedef std::map<std::string, bufferlist> Metadata;
+
+ 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(MockExclusiveLock &mock_exclusive_lock,
+ librbd::io::Direction direction) {
+ EXPECT_CALL(mock_exclusive_lock, set_require_lock(true, direction, _))
+ .WillOnce(WithArg<2>(Invoke([](Context* ctx) { ctx->complete(0); })));
+ }
+
+ void expect_unset_require_lock(MockExclusiveLock &mock_exclusive_lock,
+ librbd::io::Direction direction) {
+ EXPECT_CALL(mock_exclusive_lock, unset_require_lock(direction));
+ }
+
+ 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,
+ MockGetMetadataRequest& mock_request,
+ const std::string& oid,
+ const Metadata& metadata, int r) {
+ EXPECT_CALL(mock_request, send())
+ .WillOnce(Invoke([&mock_image_ctx, &mock_request, oid, metadata, r]() {
+ ASSERT_EQ(oid, mock_request.oid);
+ *mock_request.pairs = metadata;
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ mock_request.on_finish, r);
+ }));
+ }
+
+ 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.image_watcher, is_unregistered())
+ .WillOnce(Return(false));
+ 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_image_dispatcher, 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_image_dispatcher, unblock_writes())
+ .Times(1);
+ }
+
+ void expect_image_flush(MockImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, send(_))
+ .WillOnce(Invoke([r](io::ImageDispatchSpec* spec) {
+ ASSERT_TRUE(boost::get<io::ImageDispatchSpec::Flush>(
+ &spec->request) != nullptr);
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ spec->aio_comp->set_request_count(1);
+ spec->aio_comp->add_request();
+ spec->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);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ }
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ C_SaferCond ctx;
+ MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx);
+ req->send();
+
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageRefreshRequest, SnapshotV2EnoentRetriesLimit) {
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_op_work_queue(mock_image_ctx);
+ expect_test_features(mock_image_ctx);
+
+ InSequence seq;
+ for (int i = 0; i < RefreshRequest<>::MAX_ENOENT_RETRIES + 1; ++i) {
+ expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0);
+ expect_get_parent(mock_image_ctx, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 0);
+ expect_apply_metadata(mock_image_ctx, 0);
+ expect_get_group(mock_image_ctx, 0);
+ expect_get_snapshots(mock_image_ctx, false, -ENOENT);
+ }
+
+ C_SaferCond ctx;
+ auto req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx);
+ req->send();
+
+ ASSERT_EQ(-ENOENT, 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);
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 0);
+ expect_apply_metadata(mock_image_ctx, 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_CALL(mock_image_ctx, rebuild_data_io_context());
+ 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);
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 0);
+ expect_apply_metadata(mock_image_ctx, 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);
+ }
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ C_SaferCond ctx;
+ MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, true, &ctx);
+ req->send();
+
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageRefreshRequest, SuccessChildBeingFlattened) {
+ 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);
+ auto mock_refresh_parent_request = new MockRefreshParentRequest();
+ MockRefreshParentRequest mock_refresh_parent_request_ext;
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 0);
+ expect_apply_metadata(mock_image_ctx, 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,
+ -ENOENT);
+ 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, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 0);
+ expect_apply_metadata(mock_image_ctx, 0);
+ expect_get_group(mock_image_ctx, 0);
+ expect_refresh_parent_is_required(mock_refresh_parent_request_ext, false);
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0);
+ }
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ C_SaferCond ctx;
+ auto req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx);
+ req->send();
+
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageRefreshRequest, ChildEnoentRetriesLimit) {
+ 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);
+ constexpr int num_tries = RefreshRequest<>::MAX_ENOENT_RETRIES + 1;
+ MockRefreshParentRequest* mock_refresh_parent_requests[num_tries];
+ for (auto& mock_refresh_parent_request : mock_refresh_parent_requests) {
+ mock_refresh_parent_request = new MockRefreshParentRequest();
+ }
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_op_work_queue(mock_image_ctx);
+ expect_test_features(mock_image_ctx);
+
+ mock_image_ctx.features &= ~RBD_FEATURE_OPERATIONS;
+
+ InSequence seq;
+ for (auto mock_refresh_parent_request : mock_refresh_parent_requests) {
+ 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, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 0);
+ expect_apply_metadata(mock_image_ctx, 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,
+ -ENOENT);
+ }
+ expect_refresh_parent_apply(*mock_refresh_parent_requests[num_tries - 1]);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+ expect_refresh_parent_finalize(
+ mock_image_ctx, *mock_refresh_parent_requests[num_tries - 1], 0);
+
+ C_SaferCond ctx;
+ auto req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx);
+ req->send();
+
+ ASSERT_EQ(-ENOENT, 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ }
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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;
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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_CALL(mock_image_ctx, rebuild_data_io_context());
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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_exclusive_lock, librbd::io::DIRECTION_BOTH);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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;
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+ if (!mock_image_ctx.clone_copy_on_read) {
+ expect_unset_require_lock(mock_exclusive_lock, librbd::io::DIRECTION_READ);
+ }
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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;
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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_CALL(mock_image_ctx, rebuild_data_io_context());
+ 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;
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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, -EBLOCKLISTED);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ C_SaferCond ctx;
+ MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-EBLOCKLISTED, 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;
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ 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);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ }
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ C_SaferCond ctx;
+ MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx);
+ req->send();
+
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageRefreshRequest, NonPrimaryFeature) {
+ 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;
+
+ // ensure the image is put into read-only mode
+ expect_get_mutable_metadata(mock_image_ctx,
+ ictx->features | RBD_FEATURE_NON_PRIMARY, 0);
+ expect_get_parent(mock_image_ctx, 0);
+ MockGetMetadataRequest mock_get_metadata_request;
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ C_SaferCond ctx1;
+ auto req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx1);
+ req->send();
+
+ ASSERT_EQ(0, ctx1.wait());
+ ASSERT_TRUE(mock_image_ctx.read_only);
+ ASSERT_EQ(IMAGE_READ_ONLY_FLAG_NON_PRIMARY, mock_image_ctx.read_only_flags);
+
+ // try again but permit R/W against non-primary image
+ mock_image_ctx.read_only_mask = ~IMAGE_READ_ONLY_FLAG_NON_PRIMARY;
+
+ expect_get_mutable_metadata(mock_image_ctx,
+ ictx->features | RBD_FEATURE_NON_PRIMARY, 0);
+ expect_get_parent(mock_image_ctx, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request,
+ mock_image_ctx.header_oid, {}, 0);
+ expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {},
+ 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);
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0);
+ }
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+
+ C_SaferCond ctx2;
+ req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx2);
+ req->send();
+
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_FALSE(mock_image_ctx.read_only);
+ ASSERT_EQ(0U, mock_image_ctx.read_only_flags);
+}
+
+} // 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 000000000..9700202d6
--- /dev/null
+++ b/src/test/librbd/image/test_mock_RemoveRequest.cc
@@ -0,0 +1,480 @@
+// -*- 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/journal/TypeTraits.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
+
+template<>
+struct Journal<MockTestImageCtx> {
+ static void get_work_queue(CephContext*, MockContextWQ**) {
+ }
+};
+
+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 journal {
+
+template <>
+struct TypeTraits<MockTestImageCtx> {
+ typedef librbd::MockContextWQ ContextWQ;
+};
+
+} // namespace journal
+
+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);
+ }));
+ }
+
+ void expect_state_close(MockTestImageCtx &mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.state, close(_))
+ .WillOnce(Invoke([](Context *on_ready) {
+ on_ready->complete(0);
+ }));
+ }
+
+ 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 000000000..f5204ac20
--- /dev/null
+++ b/src/test/librbd/image/test_mock_ValidatePoolRequest.cc
@@ -0,0 +1,223 @@
+// -*- 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();
+ m_ioctx.remove(RBD_INFO);
+ 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, &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, &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, &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, &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, &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, &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, &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, &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 000000000..8c2e07f4e
--- /dev/null
+++ b/src/test/librbd/io/test_mock_CopyupRequest.cc
@@ -0,0 +1,1334 @@
+// -*- 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/api/Io.h"
+#include "librbd/deep_copy/ObjectCopyRequest.h"
+#include "librbd/io/CopyupRequest.h"
+#include "librbd/io/ImageDispatchSpec.h"
+#include "librbd/io/ObjectRequest.h"
+#include "librbd/io/ReadResult.h"
+#include "librbd/io/Utils.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;
+ }
+ ~MockTestImageCtx() override {
+ // copyups need to complete prior to attempting to delete this object
+ wait_for_async_ops();
+ }
+
+ 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, uint32_t flags,
+ Handler*, Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->object_number = object_number;
+ s_instance->flatten = (
+ (flags & deep_copy::OBJECT_COPY_REQUEST_FLAG_FLATTEN) != 0);
+ 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 {
+
+namespace util {
+
+template <> void file_to_extents(
+ MockTestImageCtx* image_ctx, uint64_t offset, uint64_t length,
+ uint64_t buffer_offset,
+ striper::LightweightObjectExtents* object_extents) {
+ Striper::file_to_extents(image_ctx->cct, &image_ctx->layout, offset, length,
+ 0, buffer_offset, object_extents);
+}
+
+template <> void extent_to_file(
+ MockTestImageCtx* image_ctx, uint64_t object_no, uint64_t offset,
+ uint64_t length,
+ std::vector<std::pair<uint64_t, uint64_t> >& extents) {
+ Striper::extent_to_file(image_ctx->cct, &image_ctx->layout, object_no,
+ offset, length, extents);
+}
+
+} // namespace util
+
+template <>
+struct ObjectRequest<librbd::MockTestImageCtx> {
+ static void add_write_hint(librbd::MockTestImageCtx&,
+ neorados::WriteOp*) {
+ }
+};
+
+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(neorados::WriteOp*));
+};
+
+} // 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"
+
+MATCHER_P(IsRead, image_extents, "") {
+ auto req = boost::get<librbd::io::ImageDispatchSpec::Read>(&arg->request);
+ return (req != nullptr && image_extents == arg->image_extents);
+}
+
+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 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) {
+ 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) {
+ EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, overlap))
+ .WillOnce(WithoutArgs(Invoke([object_overlap]() {
+ return object_overlap;
+ })));
+ }
+
+ void expect_read_parent(librbd::MockTestImageCtx& mock_image_ctx,
+ const Extents& image_extents,
+ const std::string& data, int r) {
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher,
+ send(IsRead(image_extents)))
+ .WillOnce(Invoke(
+ [&mock_image_ctx, image_extents, data, r](io::ImageDispatchSpec* spec) {
+ auto req = boost::get<librbd::io::ImageDispatchSpec::Read>(
+ &spec->request);
+ ASSERT_TRUE(req != nullptr);
+
+ if (r < 0) {
+ spec->fail(r);
+ return;
+ }
+
+ spec->dispatch_result = DISPATCH_RESULT_COMPLETE;
+
+ auto aio_comp = spec->aio_comp;
+ aio_comp->read_result = std::move(req->read_result);
+ aio_comp->read_result.set_image_extents(image_extents);
+ aio_comp->set_request_count(1);
+ auto ctx = new ReadResult::C_ImageReadRequest(aio_comp, 0,
+ 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;
+ }
+
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ EXPECT_CALL(mock_io_ctx,
+ exec(oid, _, StrEq("rbd"), StrEq("copyup"),
+ ContentsEqual(in_bl), _, _, snapc))
+ .WillOnce(Return(r));
+ }
+
+ void expect_sparse_copyup(MockTestImageCtx &mock_image_ctx, uint64_t snap_id,
+ const std::string &oid,
+ const std::map<uint64_t, uint64_t> &extent_map,
+ const std::string &data, int r) {
+ bufferlist data_bl;
+ data_bl.append(data);
+
+ bufferlist in_bl;
+ encode(extent_map, in_bl);
+ encode(data_bl, in_bl);
+
+ SnapContext snapc;
+ if (snap_id == CEPH_NOSNAP) {
+ snapc = mock_image_ctx.snapc;
+ }
+
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ EXPECT_CALL(mock_io_ctx,
+ exec(oid, _, StrEq("rbd"), StrEq("sparse_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;
+ }
+
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ EXPECT_CALL(mock_io_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([](neorados::WriteOp* 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 expect_prepare_copyup(MockTestImageCtx& mock_image_ctx, int r = 0) {
+ EXPECT_CALL(*mock_image_ctx.io_object_dispatcher,
+ prepare_copyup(_, _)).WillOnce(Return(r));
+ }
+
+ void expect_prepare_copyup(MockTestImageCtx& mock_image_ctx,
+ const SparseBufferlist& in_sparse_bl,
+ const SparseBufferlist& out_sparse_bl) {
+ EXPECT_CALL(*mock_image_ctx.io_object_dispatcher,
+ prepare_copyup(_, _))
+ .WillOnce(WithArg<1>(Invoke(
+ [in_sparse_bl, out_sparse_bl]
+ (SnapshotSparseBufferlist* snap_sparse_bl) {
+ auto& sparse_bl = (*snap_sparse_bl)[0];
+ EXPECT_EQ(in_sparse_bl, sparse_bl);
+
+ sparse_bl = out_sparse_bl;
+ return 0;
+ })));
+ }
+
+ void flush_async_operations(librbd::ImageCtx* ictx) {
+ api::Io<>::flush(*ictx);
+ }
+
+ 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
+
+ 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_sparse_copyup(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0),
+ {{0, 4096}}, data, 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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->image_lock.lock();
+ 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->image_lock.unlock();
+
+ 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
+
+ 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_sparse_copyup(mock_image_ctx, 0, ictx->get_object_name(0), {{0, 4096}},
+ data, 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
+
+ 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_sparse_copyup(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0),
+ {{0, 4096}}, data, 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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->image_lock.lock();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {1, {1}};
+ ictx->image_lock.unlock();
+
+ 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
+
+ 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_sparse_copyup(mock_image_ctx, 0, ictx->get_object_name(0), {{0, 4096}},
+ data, 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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_sparse_copyup(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0),
+ {}, "", 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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->image_lock.lock();
+ 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->image_lock.unlock();
+
+ 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_sparse_copyup(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0),
+ {}, "", 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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->image_lock.lock();
+ 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->image_lock.unlock();
+
+ 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_sparse_copyup(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0),
+ {}, "", 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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_prepare_copyup(mock_image_ctx);
+ 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_sparse_copyup(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0),
+ {}, "", 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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;
+
+ std::string data(4096, '\0');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
+
+ 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_sparse_copyup(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0),
+ {}, "", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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;
+
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, "", -ENOENT);
+
+ expect_prepare_copyup(mock_image_ctx);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_is_empty_write_op(mock_write_request, true);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
+
+ 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, 0,
+ {{0, 4096}}, {});
+ expect_add_copyup_ops(mock_write_request1);
+ expect_sparse_copyup(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0),
+ {{0, 4096}}, data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request2;
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ EXPECT_CALL(mock_io_ctx, write(ictx->get_object_name(0), _, 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;
+
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, "", -EPERM);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ MockAbstractObjectWriteRequest mock_write_request;
+ req->append_request(&mock_write_request, {});
+ req->send();
+
+ ASSERT_EQ(-EPERM, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, PrepareCopyupError) {
+ 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx, -EIO);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ MockAbstractObjectWriteRequest mock_write_request;
+ req->append_request(&mock_write_request, {});
+ req->send();
+
+ ASSERT_EQ(-EIO, 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, 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
+
+ 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, 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->image_lock.lock();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {1, {1}};
+ ictx->image_lock.unlock();
+
+ 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
+
+ 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_sparse_copyup(mock_image_ctx, 0, ictx->get_object_name(0), {{0, 4096}},
+ data, -EPERM);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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);
+}
+
+TEST_F(TestMockIoCopyupRequest, SparseCopyupNotSupported) {
+ 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);
+ mock_image_ctx.enable_sparse_copyup = false;
+
+ 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
+
+ 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, ictx->get_object_name(0), data, 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 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, ProcessCopyup) {
+ 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+
+ bufferlist in_prepare_bl;
+ in_prepare_bl.append(std::string(3072, '1'));
+ bufferlist out_prepare_bl;
+ out_prepare_bl.substr_of(in_prepare_bl, 0, 1024);
+ expect_prepare_copyup(
+ mock_image_ctx,
+ {{1024U, {3072U, {SPARSE_EXTENT_STATE_DATA, 3072,
+ std::move(in_prepare_bl)}}}},
+ {{2048U, {1024U, {SPARSE_EXTENT_STATE_DATA, 1024,
+ std::move(out_prepare_bl)}}}});
+
+ 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_sparse_copyup(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0),
+ {{2048, 1024}}, data.substr(0, 1024), 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request, {{0, 1024}});
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, ProcessCopyupOverwrite) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->image_lock.lock();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {1, {1}};
+ ictx->image_lock.unlock();
+
+ 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+
+ bufferlist in_prepare_bl;
+ in_prepare_bl.append(data);
+ bufferlist out_prepare_bl;
+ out_prepare_bl.substr_of(in_prepare_bl, 0, 1024);
+ expect_prepare_copyup(
+ mock_image_ctx,
+ {{0, {4096, {SPARSE_EXTENT_STATE_DATA, 4096,
+ std::move(in_prepare_bl)}}}},
+ {{0, {1024, {SPARSE_EXTENT_STATE_DATA, 1024, bufferlist{out_prepare_bl}}}},
+ {2048, {1024, {SPARSE_EXTENT_STATE_DATA, 1024,
+ bufferlist{out_prepare_bl}}}}});
+
+ 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_sparse_copyup(mock_image_ctx, 0, ictx->get_object_name(0),
+ {{0, 1024}, {2048, 1024}}, data.substr(0, 2048), 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request, {{0, 1024}});
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+} // 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 000000000..d1390bcf8
--- /dev/null
+++ b/src/test/librbd/io/test_mock_ImageRequest.cc
@@ -0,0 +1,562 @@
+// -*- 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"
+#include "librbd/io/Utils.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 {
+
+namespace util {
+
+template<>
+void file_to_extents(
+ MockTestImageCtx *image_ctx, uint64_t offset, uint64_t length,
+ uint64_t buffer_offset,
+ striper::LightweightObjectExtents *object_extents) {
+ Striper::file_to_extents(image_ctx->cct, &image_ctx->layout, offset, length,
+ 0, buffer_offset, object_extents);
+}
+
+template <> void extent_to_file(
+ MockTestImageCtx* image_ctx, uint64_t object_no, uint64_t offset,
+ uint64_t length,
+ std::vector<std::pair<uint64_t, uint64_t> >& extents) {
+ Striper::extent_to_file(image_ctx->cct, &image_ctx->layout, object_no,
+ offset, length, extents);
+}
+
+} // namespace util
+
+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;
+ typedef ImageListSnapsRequest<librbd::MockTestImageCtx> MockImageListSnapsRequest;
+
+ 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);
+ }));
+ }
+
+ void expect_object_list_snaps_request(MockTestImageCtx &mock_image_ctx,
+ uint64_t object_no,
+ const SnapshotDelta& snap_delta,
+ int r) {
+ EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, send(_))
+ .WillOnce(
+ Invoke([&mock_image_ctx, object_no, snap_delta, r]
+ (ObjectDispatchSpec* spec) {
+ auto request = boost::get<
+ librbd::io::ObjectDispatchSpec::ListSnapsRequest>(
+ &spec->request);
+ ASSERT_TRUE(request != nullptr);
+ ASSERT_EQ(object_no, request->object_no);
+
+ *request->snapshot_delta = snap_delta;
+ 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),
+ mock_image_ctx.get_data_io_context(), 0, {});
+ {
+ std::shared_lock 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),
+ mock_image_ctx.get_data_io_context(), 0, {});
+ {
+ std::shared_lock 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),
+ mock_image_ctx.get_data_io_context(), 0, 0, {});
+ {
+ std::shared_lock 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),
+ mock_image_ctx.get_data_io_context(), 0, 0, {});
+ {
+ std::shared_lock 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, mock_image_ctx.get_data_io_context(), {});
+ {
+ std::shared_lock 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, mock_image_ctx.get_data_io_context(), {});
+ {
+ std::shared_lock 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, mock_image_ctx.get_data_io_context(), {});
+ {
+ std::shared_lock 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),
+ mock_image_ctx.get_data_io_context(), 0, {});
+ {
+ std::shared_lock 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,
+ mock_image_ctx.get_data_io_context(), {});
+ {
+ std::shared_lock 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, {});
+ {
+ std::shared_lock 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),
+ mock_image_ctx.get_data_io_context(), 0, {});
+ {
+ std::shared_lock 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, mock_image_ctx.get_data_io_context(), 0, {});
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ mock_aio_image_write.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx.wait());
+}
+
+TEST_F(TestMockIoImageRequest, ListSnaps) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.layout.object_size = 16384;
+ mock_image_ctx.layout.stripe_unit = 4096;
+ mock_image_ctx.layout.stripe_count = 2;
+
+ InSequence seq;
+
+ SnapshotDelta object_snapshot_delta;
+ object_snapshot_delta[{5,6}].insert(
+ 0, 1024, {SPARSE_EXTENT_STATE_DATA, 1024});
+ object_snapshot_delta[{5,5}].insert(
+ 4096, 4096, {SPARSE_EXTENT_STATE_ZEROED, 4096});
+ expect_object_list_snaps_request(mock_image_ctx, 0, object_snapshot_delta, 0);
+ object_snapshot_delta = {};
+ object_snapshot_delta[{5,6}].insert(
+ 1024, 3072, {SPARSE_EXTENT_STATE_DATA, 3072});
+ object_snapshot_delta[{5,5}].insert(
+ 2048, 2048, {SPARSE_EXTENT_STATE_ZEROED, 2048});
+ expect_object_list_snaps_request(mock_image_ctx, 1, object_snapshot_delta, 0);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond aio_comp_ctx;
+ AioCompletion *aio_comp = AioCompletion::create_and_start(
+ &aio_comp_ctx, ictx, AIO_TYPE_GENERIC);
+ MockImageListSnapsRequest mock_image_list_snaps_request(
+ mock_image_ctx, aio_comp, {{0, 16384}, {16384, 16384}}, {0, CEPH_NOSNAP},
+ 0, &snapshot_delta, {});
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ mock_image_list_snaps_request.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx.wait());
+
+ SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{5,6}].insert(
+ 0, 1024, {SPARSE_EXTENT_STATE_DATA, 1024});
+ expected_snapshot_delta[{5,6}].insert(
+ 5120, 3072, {SPARSE_EXTENT_STATE_DATA, 3072});
+ expected_snapshot_delta[{5,5}].insert(
+ 6144, 6144, {SPARSE_EXTENT_STATE_ZEROED, 6144});
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+}
+
+} // 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 000000000..c9fa33c43
--- /dev/null
+++ b/src/test/librbd/io/test_mock_ObjectRequest.cc
@@ -0,0 +1,1961 @@
+// -*- 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"
+#include "librbd/io/Utils.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_METHOD2(append_request, void(AbstractObjectWriteRequest<librbd::MockTestImageCtx>*,
+ const Extents&));
+};
+
+template <>
+struct CopyupRequest<librbd::MockTestImageCtx> : public CopyupRequest<librbd::MockImageCtx> {
+ static CopyupRequest* s_instance;
+ static CopyupRequest* create(librbd::MockTestImageCtx *ictx,
+ uint64_t objectno, Extents &&image_extents,
+ const ZTracer::Trace &parent_trace) {
+ return s_instance;
+ }
+
+ CopyupRequest() {
+ s_instance = this;
+ }
+};
+
+CopyupRequest<librbd::MockTestImageCtx>* CopyupRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+template <>
+struct ImageListSnapsRequest<librbd::MockTestImageCtx> {
+ static ImageListSnapsRequest* s_instance;
+
+ AioCompletion* aio_comp;
+ Extents image_extents;
+ SnapshotDelta* snapshot_delta;
+
+ ImageListSnapsRequest() {
+ s_instance = this;
+ }
+ ImageListSnapsRequest(
+ librbd::MockImageCtx& image_ctx, AioCompletion* aio_comp,
+ Extents&& image_extents, SnapIds&& snap_ids, int list_snaps_flags,
+ SnapshotDelta* snapshot_delta, const ZTracer::Trace& parent_trace) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->aio_comp = aio_comp;
+ s_instance->image_extents = image_extents;
+ s_instance->snapshot_delta = snapshot_delta;
+ }
+
+
+ MOCK_METHOD0(execute_send, void());
+ void send() {
+ ceph_assert(s_instance != nullptr);
+ s_instance->execute_send();
+ }
+};
+
+ImageListSnapsRequest<librbd::MockTestImageCtx>* ImageListSnapsRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+namespace util {
+
+template <> void file_to_extents(
+ MockTestImageCtx* image_ctx, uint64_t offset, uint64_t length,
+ uint64_t buffer_offset,
+ striper::LightweightObjectExtents* object_extents) {
+ Striper::file_to_extents(image_ctx->cct, &image_ctx->layout, offset, length,
+ 0, buffer_offset, object_extents);
+}
+
+template <> void extent_to_file(
+ MockTestImageCtx* image_ctx, uint64_t object_no, uint64_t offset,
+ uint64_t length,
+ std::vector<std::pair<uint64_t, uint64_t> >& extents) {
+ Striper::extent_to_file(image_ctx->cct, &image_ctx->layout, object_no,
+ offset, length, extents);
+}
+
+namespace {
+
+struct Mock {
+ static Mock* s_instance;
+
+ Mock() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD6(read_parent,
+ void(librbd::MockTestImageCtx *, uint64_t, ReadExtents*,
+ librados::snap_t, const ZTracer::Trace &, Context*));
+};
+
+Mock *Mock::s_instance = nullptr;
+
+} // anonymous namespace
+
+template<> void read_parent(
+ librbd::MockTestImageCtx *image_ctx, uint64_t object_no,
+ ReadExtents* extents, librados::snap_t snap_id,
+ const ZTracer::Trace &trace, Context* on_finish) {
+ Mock::s_instance->read_parent(image_ctx, object_no, extents, snap_id, trace,
+ on_finish);
+}
+
+} // namespace util
+
+} // 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 ObjectListSnapsRequest<librbd::MockTestImageCtx> MockObjectListSnapsRequest;
+ typedef AbstractObjectWriteRequest<librbd::MockTestImageCtx> MockAbstractObjectWriteRequest;
+ typedef CopyupRequest<librbd::MockTestImageCtx> MockCopyupRequest;
+ typedef ImageListSnapsRequest<librbd::MockTestImageCtx> MockImageListSnapsRequest;
+ typedef util::Mock MockUtils;
+
+ 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& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ auto& expect = EXPECT_CALL(mock_io_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& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ auto& expect = EXPECT_CALL(mock_io_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_read_parent(MockUtils &mock_utils, uint64_t object_no,
+ ReadExtents* extents, librados::snap_t snap_id,
+ int r) {
+ EXPECT_CALL(mock_utils,
+ read_parent(_, object_no, extents, snap_id, _, _))
+ .WillOnce(WithArg<5>(CompleteContext(r, static_cast<asio::ContextWQ*>(nullptr))));
+ }
+
+ 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(WithArg<0>(
+ 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) {
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ EXPECT_CALL(mock_io_ctx, assert_exists(_, _))
+ .WillOnce(Return(r));
+ }
+
+ void expect_write(MockTestImageCtx &mock_image_ctx,
+ uint64_t offset, uint64_t length, int r) {
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ auto &expect = EXPECT_CALL(mock_io_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& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ auto &expect = EXPECT_CALL(mock_io_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& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ auto &expect = EXPECT_CALL(mock_io_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& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ auto &expect = EXPECT_CALL(mock_io_ctx, remove(_, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ void expect_create(MockTestImageCtx &mock_image_ctx, bool exclusive) {
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ EXPECT_CALL(mock_io_ctx, create(_, exclusive, _))
+ .Times(1);
+ }
+
+ void expect_truncate(MockTestImageCtx &mock_image_ctx, int offset, int r) {
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ auto &expect = EXPECT_CALL(mock_io_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& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ auto &expect = EXPECT_CALL(mock_io_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& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ auto &expect = EXPECT_CALL(mock_io_ctx, cmpext(_, offset, _, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ void expect_list_snaps(MockTestImageCtx &mock_image_ctx,
+ const librados::snap_set_t& snap_set, int r) {
+ auto io_context = *mock_image_ctx.get_data_io_context();
+ io_context.read_snap(CEPH_SNAPDIR);
+ auto& mock_io_ctx = librados::get_mock_io_ctx(mock_image_ctx.rados_api,
+ io_context);
+ EXPECT_CALL(mock_io_ctx, list_snaps(_, _))
+ .WillOnce(WithArg<1>(Invoke(
+ [snap_set, r](librados::snap_set_t* out_snap_set) {
+ *out_snap_set = snap_set;
+ return r;
+ })));
+ }
+
+ void expect_image_list_snaps(MockImageListSnapsRequest& req,
+ const Extents& image_extents,
+ const SnapshotDelta& image_snapshot_delta,
+ int r) {
+ EXPECT_CALL(req, execute_send())
+ .WillOnce(Invoke(
+ [&req, image_extents, image_snapshot_delta, r]() {
+ ASSERT_EQ(image_extents, req.image_extents);
+ *req.snapshot_delta = image_snapshot_delta;
+
+ auto aio_comp = req.aio_comp;
+ aio_comp->set_request_count(1);
+ aio_comp->add_request();
+ aio_comp->complete_request(r);
+ }));
+ }
+};
+
+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);
+ expect_read(mock_image_ctx, ictx->get_object_name(0), 8192, 4096,
+ std::string(4096, '2'), 0);
+
+ C_SaferCond ctx;
+ uint64_t version;
+ ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, 0, &extents,
+ mock_image_ctx.get_data_io_context(), 0, 0, {},
+ &version, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ bufferlist expected_bl1;
+ expected_bl1.append(std::string(4096, '1'));
+ bufferlist expected_bl2;
+ expected_bl2.append(std::string(4096, '2'));
+
+ ASSERT_EQ(extents[0].extent_map.size(), 0);
+ ASSERT_EQ(extents[1].extent_map.size(), 0);
+ ASSERT_TRUE(extents[0].bl.contents_equal(expected_bl1));
+ ASSERT_TRUE(extents[1].bl.contents_equal(expected_bl2));
+}
+
+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);
+
+ C_SaferCond ctx;
+
+ ReadExtents extents = {{0, ictx->sparse_read_threshold_bytes}};
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, 0, &extents,
+ mock_image_ctx.get_data_io_context(), 0, 0, {}, nullptr, &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);
+
+ C_SaferCond ctx;
+ ReadExtents extents = {{0, 4096}};
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, 0, &extents,
+ mock_image_ctx.get_data_io_context(), 0, 0, {},
+ nullptr, &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);
+
+ MockUtils mock_utils;
+ ReadExtents extents = {{0, 4096}};
+ expect_read_parent(mock_utils, 0, &extents, CEPH_NOSNAP, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, 0, &extents,
+ mock_image_ctx.get_data_io_context(), 0, 0, {},
+ nullptr, &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);
+
+ MockUtils mock_utils;
+ ReadExtents extents = {{0, 4096}};
+ expect_read_parent(mock_utils, 0, &extents, CEPH_NOSNAP, -EPERM);
+
+ C_SaferCond ctx;
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, 0, &extents,
+ mock_image_ctx.get_data_io_context(), 0, 0, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, SkipParentRead) {
+ 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);
+
+ ReadExtents extents = {{0, 4096}};
+ C_SaferCond ctx;
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, 0, &extents, mock_image_ctx.get_data_io_context(), 0,
+ READ_FLAG_DISABLE_READ_FROM_PARENT, {}, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-ENOENT, 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);
+
+ MockUtils mock_utils;
+ ReadExtents extents = {{0, 4096}};
+ expect_read_parent(mock_utils, 0, &extents, CEPH_NOSNAP, 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);
+
+ C_SaferCond ctx;
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, 0, &extents,
+ mock_image_ctx.get_data_io_context(), 0, 0, {},
+ nullptr, &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, 0, 0, std::move(bl), mock_image_ctx.get_data_io_context(),
+ 0, 0, std::nullopt, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, WriteWithCreateExclusiveFlag) {
+ 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;
+ }
+
+ // exclusive create should succeed
+ {
+ 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, 0, 0, std::move(bl),
+ mock_image_ctx.get_data_io_context(), 0,
+ OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, std::nullopt, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+ }
+
+ // exclusive create should fail since object already exists
+ {
+ 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);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, 0, 0, std::move(bl),
+ mock_image_ctx.get_data_io_context(), 0,
+ OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, std::nullopt, {}, &ctx);
+ req->send();
+ ASSERT_EQ(-EEXIST, ctx.wait());
+ }
+}
+
+TEST_F(TestMockIoObjectRequest, WriteWithAssertVersion) {
+ 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;
+ }
+
+ // write an object
+ {
+ 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, 0, 0, std::move(bl),
+ mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+ }
+
+ // assert version should succeed (version = 1)
+ {
+ 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, 0, 0, std::move(bl),
+ mock_image_ctx.get_data_io_context(), 0, 0,
+ std::make_optional(1), {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+ }
+
+ // assert with wrong (lower) version (version = 2)
+ {
+ 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);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, 0, 0, std::move(bl),
+ mock_image_ctx.get_data_io_context(), 0, 0,
+ std::make_optional(1), {}, &ctx);
+ req->send();
+ ASSERT_EQ(-ERANGE, ctx.wait());
+ }
+
+ // assert with wrong (higher) version (version = 2)
+ {
+ 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);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, 0, 0, std::move(bl),
+ mock_image_ctx.get_data_io_context(), 0, 0, std::make_optional(3),
+ {}, &ctx);
+ req->send();
+ ASSERT_EQ(-EOVERFLOW, 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, 0, 0, std::move(bl), mock_image_ctx.get_data_io_context(),
+ 0, 0, std::nullopt, {}, &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, 0, 0, std::move(bl), mock_image_ctx.get_data_io_context(),
+ 0, 0, std::nullopt, {}, &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, 0, 0, std::move(bl), mock_image_ctx.get_data_io_context(),
+ 0, 0, std::nullopt, {}, &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, 0, 0, std::move(bl), mock_image_ctx.get_data_io_context(),
+ 0, 0, std::nullopt, {}, &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, 0, 0, std::move(bl), mock_image_ctx.get_data_io_context(),
+ 0, 0, std::nullopt, {}, &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, 0, 0, std::move(bl), mock_image_ctx.get_data_io_context(),
+ 0, 0, std::nullopt, {}, &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, 0, 0, std::move(bl), mock_image_ctx.get_data_io_context(),
+ 0, 0, std::nullopt, {}, &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, 0, 0, mock_image_ctx.get_object_size(),
+ mock_image_ctx.get_data_io_context(), 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, 0, 0, mock_image_ctx.get_object_size(),
+ mock_image_ctx.get_data_io_context(),
+ 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, 0, 0, mock_image_ctx.get_object_size(),
+ mock_image_ctx.get_data_io_context(),
+ 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, 0, 1, mock_image_ctx.get_object_size() - 1,
+ mock_image_ctx.get_data_io_context(), 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, 0, 1, 1, mock_image_ctx.get_data_io_context(), 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, 0, 0, mock_image_ctx.get_object_size(),
+ mock_image_ctx.get_data_io_context(),
+ 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, 0, 0, mock_image_ctx.get_object_size(),
+ mock_image_ctx.get_data_io_context(),
+ 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, 0, 0, 4096, std::move(bl),
+ mock_image_ctx.get_data_io_context(), 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, 0, 0, std::move(cmp_bl), std::move(bl),
+ mock_image_ctx.get_data_io_context(), &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, 0, 0, std::move(cmp_bl), std::move(bl),
+ mock_image_ctx.get_data_io_context(), &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, 0, 0, std::move(cmp_bl), std::move(bl),
+ mock_image_ctx.get_data_io_context(), &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, 0, 0, std::move(cmp_bl), std::move(bl),
+ mock_image_ctx.get_data_io_context(), &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,
+ -EBLOCKLISTED);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.get_data_io_context(),
+ 0, 0, std::nullopt, {}, &ctx);
+ req->send();
+ ASSERT_EQ(-EBLOCKLISTED, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, ListSnaps) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.snaps = {3, 4, 5, 6, 7};
+
+ librados::snap_set_t snap_set;
+ snap_set.seq = 6;
+ librados::clone_info_t clone_info;
+
+ clone_info.cloneid = 3;
+ clone_info.snaps = {3};
+ clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{
+ {0, 4194304}};
+ clone_info.size = 4194304;
+ snap_set.clones.push_back(clone_info);
+
+ clone_info.cloneid = 4;
+ clone_info.snaps = {4};
+ clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{
+ {278528, 4096}, {442368, 4096}, {1859584, 4096}, {2224128, 4096},
+ {2756608, 4096}, {3227648, 4096}, {3739648, 4096}, {3903488, 4096}};
+ clone_info.size = 4194304;
+ snap_set.clones.push_back(clone_info);
+
+ clone_info.cloneid = 6;
+ clone_info.snaps = {5, 6};
+ clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{
+ {425984, 4096}, {440320, 1024}, {1925120, 4096}, {2125824, 4096},
+ {2215936, 5120}, {3067904, 4096}};
+ clone_info.size = 3072000;
+ snap_set.clones.push_back(clone_info);
+
+ clone_info.cloneid = CEPH_NOSNAP;
+ clone_info.snaps = {};
+ clone_info.overlap = {};
+ clone_info.size = 4194304;
+ snap_set.clones.push_back(clone_info);
+
+ expect_list_snaps(mock_image_ctx, snap_set, 0);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0,
+ {{440320, 1024}, {2122728, 1024}, {2220032, 2048}, {3072000, 4096}},
+ {3, 4, 5, 6, 7, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{5,6}].insert(
+ 440320, 1024, {SPARSE_EXTENT_STATE_DATA, 1024});
+ expected_snapshot_delta[{5,6}].insert(
+ 2122728, 1024, {SPARSE_EXTENT_STATE_DATA, 1024});
+ expected_snapshot_delta[{5,6}].insert(
+ 2220032, 2048, {SPARSE_EXTENT_STATE_DATA, 2048});
+ expected_snapshot_delta[{7,CEPH_NOSNAP}].insert(
+ 2122728, 1024, {SPARSE_EXTENT_STATE_DATA, 1024});
+ expected_snapshot_delta[{7,CEPH_NOSNAP}].insert(
+ 2221056, 1024, {SPARSE_EXTENT_STATE_DATA, 1024});
+ expected_snapshot_delta[{7,CEPH_NOSNAP}].insert(
+ 3072000, 4096, {SPARSE_EXTENT_STATE_DATA, 4096});
+ expected_snapshot_delta[{5,5}].insert(
+ 3072000, 4096, {SPARSE_EXTENT_STATE_ZEROED, 4096});
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+}
+
+TEST_F(TestMockIoObjectRequest, ListSnapsENOENT) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ expect_list_snaps(mock_image_ctx, {}, -ENOENT);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0,
+ {{440320, 1024}},
+ {0, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{0,0}].insert(
+ 440320, 1024, {SPARSE_EXTENT_STATE_DNE, 1024});
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+}
+
+TEST_F(TestMockIoObjectRequest, ListSnapsDNE) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.snaps = {2, 3, 4};
+
+ librados::snap_set_t snap_set;
+ snap_set.seq = 6;
+ librados::clone_info_t clone_info;
+
+ clone_info.cloneid = 4;
+ clone_info.snaps = {3, 4};
+ clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{
+ {0, 4194304}};
+ clone_info.size = 4194304;
+ snap_set.clones.push_back(clone_info);
+
+ expect_list_snaps(mock_image_ctx, snap_set, 0);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0,
+ {{440320, 1024}},
+ {2, 3, 4}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{2,2}].insert(
+ 440320, 1024, {SPARSE_EXTENT_STATE_DNE, 1024});
+ expected_snapshot_delta[{3,4}].insert(
+ 440320, 1024, {SPARSE_EXTENT_STATE_DATA, 1024});
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+}
+
+TEST_F(TestMockIoObjectRequest, ListSnapsEmpty) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ expect_list_snaps(mock_image_ctx, {}, 0);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0,
+ {{440320, 1024}},
+ {0, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{0,0}].insert(
+ 440320, 1024, {SPARSE_EXTENT_STATE_ZEROED, 1024});
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+}
+
+TEST_F(TestMockIoObjectRequest, ListSnapsError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ expect_list_snaps(mock_image_ctx, {}, -EPERM);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0,
+ {{440320, 1024}, {2122728, 1024}, {2220032, 2048}, {3072000, 4096}},
+ {3, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, ListSnapsParent) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.parent = &mock_image_ctx;
+
+ InSequence seq;
+
+ expect_list_snaps(mock_image_ctx, {}, -ENOENT);
+
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+
+ MockImageListSnapsRequest mock_image_list_snaps_request;
+ SnapshotDelta image_snapshot_delta;
+ image_snapshot_delta[{1,6}].insert(
+ 0, 1024, {SPARSE_EXTENT_STATE_DATA, 1024});
+ expect_image_list_snaps(mock_image_list_snaps_request,
+ {{0, 4096}}, image_snapshot_delta, 0);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0,
+ {{440320, 1024}, {2122728, 1024}, {2220032, 2048}, {3072000, 4096}},
+ {0, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{0,0}].insert(
+ 0, 1024, {SPARSE_EXTENT_STATE_DATA, 1024});
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+}
+
+TEST_F(TestMockIoObjectRequest, ListSnapsWholeObject) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.parent = &mock_image_ctx;
+
+ InSequence seq;
+
+ librados::snap_set_t snap_set;
+ snap_set.seq = 3;
+ librados::clone_info_t clone_info;
+
+ clone_info.cloneid = 3;
+ clone_info.snaps = {3};
+ clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 1}};
+ clone_info.size = 4194304;
+ snap_set.clones.push_back(clone_info);
+
+ clone_info.cloneid = CEPH_NOSNAP;
+ clone_info.snaps = {};
+ clone_info.overlap = {};
+ clone_info.size = 4194304;
+ snap_set.clones.push_back(clone_info);
+
+ expect_list_snaps(mock_image_ctx, snap_set, 0);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size - 1}},
+ {0, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert(
+ 0, mock_image_ctx.layout.object_size - 1,
+ {SPARSE_EXTENT_STATE_DATA, mock_image_ctx.layout.object_size - 1});
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+}
+
+} // namespace io
+} // namespace librbd
+
diff --git a/src/test/librbd/io/test_mock_QosImageDispatch.cc b/src/test/librbd/io/test_mock_QosImageDispatch.cc
new file mode 100644
index 000000000..acd3b97c2
--- /dev/null
+++ b/src/test/librbd/io/test_mock_QosImageDispatch.cc
@@ -0,0 +1,89 @@
+// -*- 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 io {
+
+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(IMAGE_DISPATCH_FLAG_QOS_BPS_THROTTLE, 0,
+ 0, 1);
+
+ 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);
+ 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(IMAGE_DISPATCH_FLAG_QOS_BPS_THROTTLE, 1,
+ 0, 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(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(IMAGE_DISPATCH_FLAG_QOS_BPS_THROTTLE, 1,
+ 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(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_SimpleSchedulerObjectDispatch.cc b/src/test/librbd/io/test_mock_SimpleSchedulerObjectDispatch.cc
new file mode 100644
index 000000000..a3d823d23
--- /dev/null
+++ b/src/test/librbd/io/test_mock_SimpleSchedulerObjectDispatch.cc
@@ -0,0 +1,823 @@
+// -*- 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/MockSafeTimer.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "include/rbd/librbd.hpp"
+#include "librbd/io/ObjectDispatchSpec.h"
+#include "librbd/io/SimpleSchedulerObjectDispatch.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace io {
+
+template <>
+struct TypeTraits<MockTestImageCtx> {
+ typedef ::MockSafeTimer SafeTimer;
+};
+
+template <>
+struct FlushTracker<MockTestImageCtx> {
+ FlushTracker(MockTestImageCtx*) {
+ }
+
+ void shut_down() {
+ }
+
+ void flush(Context*) {
+ }
+
+ void start_io(uint64_t) {
+ }
+
+ void finish_io(uint64_t) {
+ }
+
+};
+
+} // namespace io
+} // namespace librbd
+
+#include "librbd/io/SimpleSchedulerObjectDispatch.cc"
+
+namespace librbd {
+namespace io {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+
+struct TestMockIoSimpleSchedulerObjectDispatch : public TestMockFixture {
+ typedef SimpleSchedulerObjectDispatch<librbd::MockTestImageCtx> MockSimpleSchedulerObjectDispatch;
+
+ MockSafeTimer m_mock_timer;
+ ceph::mutex m_mock_timer_lock =
+ ceph::make_mutex("TestMockIoSimpleSchedulerObjectDispatch::Mutex");
+
+ TestMockIoSimpleSchedulerObjectDispatch() {
+ MockTestImageCtx::set_timer_instance(&m_mock_timer, &m_mock_timer_lock);
+ EXPECT_EQ(0, _rados.conf_set("rbd_io_scheduler_simple_max_delay", "1"));
+ }
+
+ void expect_get_object_name(MockTestImageCtx &mock_image_ctx,
+ uint64_t object_no) {
+ EXPECT_CALL(mock_image_ctx, get_object_name(object_no))
+ .WillRepeatedly(Return(
+ mock_image_ctx.image_ctx->get_object_name(object_no)));
+ }
+
+ void expect_dispatch_delayed_requests(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);
+ }));
+ }
+
+ void expect_cancel_timer_task(Context *timer_task) {
+ EXPECT_CALL(m_mock_timer, cancel_event(timer_task))
+ .WillOnce(Invoke([](Context *timer_task) {
+ delete timer_task;
+ return true;
+ }));
+ }
+
+ void expect_add_timer_task(Context **timer_task) {
+ EXPECT_CALL(m_mock_timer, add_event_at(_, _))
+ .WillOnce(Invoke([timer_task](ceph::real_clock::time_point, Context *task) {
+ *timer_task = task;
+ return task;
+ }));
+ }
+
+ void expect_schedule_dispatch_delayed_requests(Context *current_task,
+ Context **new_task) {
+ if (current_task != nullptr) {
+ expect_cancel_timer_task(current_task);
+ }
+ if (new_task != nullptr) {
+ expect_add_timer_task(new_task);
+ }
+ }
+
+ void run_timer_task(Context *timer_task) {
+ std::lock_guard timer_locker{m_mock_timer_lock};
+ timer_task->complete(0);
+ }
+};
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Read) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ C_SaferCond cond;
+ Context *on_finish = &cond;
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.read(
+ 0, &extents, mock_image_ctx.get_data_io_context(), 0, 0, {}, nullptr,
+ nullptr, nullptr, &on_finish, nullptr));
+ ASSERT_EQ(on_finish, &cond); // not modified
+ on_finish->complete(0);
+ ASSERT_EQ(0, cond.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Discard) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ C_SaferCond cond;
+ Context *on_finish = &cond;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.discard(
+ 0, 0, 4096, mock_image_ctx.get_data_io_context(), 0, {}, nullptr, nullptr,
+ nullptr, &on_finish, nullptr));
+ ASSERT_NE(on_finish, &cond);
+ on_finish->complete(0);
+ ASSERT_EQ(0, cond.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Write) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ ceph::bufferlist data;
+ data.append("X");
+ int object_dispatch_flags = 0;
+ C_SaferCond cond;
+ Context *on_finish = &cond;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
+ 0, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, nullptr,
+ &on_finish, nullptr));
+ ASSERT_NE(on_finish, &cond);
+ on_finish->complete(0);
+ ASSERT_EQ(0, cond.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteSame) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ io::LightweightBufferExtents buffer_extents;
+ ceph::bufferlist data;
+ C_SaferCond cond;
+ Context *on_finish = &cond;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write_same(
+ 0, 0, 4096, std::move(buffer_extents), std::move(data),
+ mock_image_ctx.get_data_io_context(), 0, {}, nullptr, nullptr, nullptr,
+ &on_finish, nullptr));
+ ASSERT_NE(on_finish, &cond);
+ on_finish->complete(0);
+ ASSERT_EQ(0, cond.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, CompareAndWrite) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ ceph::bufferlist cmp_data;
+ ceph::bufferlist write_data;
+ C_SaferCond cond;
+ Context *on_finish = &cond;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.compare_and_write(
+ 0, 0, std::move(cmp_data), std::move(write_data),
+ mock_image_ctx.get_data_io_context(), 0, {}, nullptr, nullptr, nullptr,
+ nullptr, &on_finish, nullptr));
+ ASSERT_NE(on_finish, &cond);
+ on_finish->complete(0);
+ ASSERT_EQ(0, cond.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Flush) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ io::DispatchResult dispatch_result;
+ C_SaferCond cond;
+ Context *on_finish = &cond;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.flush(
+ FLUSH_SOURCE_USER, {}, nullptr, &dispatch_result, &on_finish, nullptr));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_EQ(on_finish, &cond); // not modified
+ on_finish->complete(0);
+ ASSERT_EQ(0, cond.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteDelayed) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ expect_get_object_name(mock_image_ctx, 0);
+
+ InSequence seq;
+
+ ceph::bufferlist data;
+ data.append("X");
+ int object_dispatch_flags = 0;
+ C_SaferCond cond1;
+ Context *on_finish1 = &cond1;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
+ 0, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, nullptr, &on_finish1,
+ nullptr));
+ ASSERT_NE(on_finish1, &cond1);
+
+ Context *timer_task = nullptr;
+ expect_schedule_dispatch_delayed_requests(nullptr, &timer_task);
+
+ io::DispatchResult dispatch_result;
+ C_SaferCond cond2;
+ Context *on_finish2 = &cond2;
+ C_SaferCond on_dispatched;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish2, &on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish2, &cond2);
+ ASSERT_NE(timer_task, nullptr);
+
+ expect_dispatch_delayed_requests(mock_image_ctx, 0);
+ expect_schedule_dispatch_delayed_requests(timer_task, nullptr);
+
+ on_finish1->complete(0);
+ ASSERT_EQ(0, cond1.wait());
+ ASSERT_EQ(0, on_dispatched.wait());
+ on_finish2->complete(0);
+ ASSERT_EQ(0, cond2.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteDelayedFlush) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ expect_get_object_name(mock_image_ctx, 0);
+
+ InSequence seq;
+
+ ceph::bufferlist data;
+ data.append("X");
+ int object_dispatch_flags = 0;
+ C_SaferCond cond1;
+ Context *on_finish1 = &cond1;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
+ 0, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, nullptr, &on_finish1,
+ nullptr));
+ ASSERT_NE(on_finish1, &cond1);
+
+ Context *timer_task = nullptr;
+ expect_schedule_dispatch_delayed_requests(nullptr, &timer_task);
+
+ io::DispatchResult dispatch_result;
+ C_SaferCond cond2;
+ Context *on_finish2 = &cond2;
+ C_SaferCond on_dispatched;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish2, &on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish2, &cond2);
+ ASSERT_NE(timer_task, nullptr);
+
+ expect_dispatch_delayed_requests(mock_image_ctx, 0);
+ expect_schedule_dispatch_delayed_requests(timer_task, nullptr);
+
+ C_SaferCond cond3;
+ Context *on_finish3 = &cond3;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.flush(
+ FLUSH_SOURCE_USER, {}, nullptr, &dispatch_result, &on_finish3, nullptr));
+ ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
+ ASSERT_EQ(on_finish3, &cond3);
+
+ on_finish1->complete(0);
+ ASSERT_EQ(0, cond1.wait());
+ ASSERT_EQ(0, on_dispatched.wait());
+ on_finish2->complete(0);
+ ASSERT_EQ(0, cond2.wait());
+ on_finish3->complete(0);
+ ASSERT_EQ(0, cond3.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteMerged) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ expect_get_object_name(mock_image_ctx, 0);
+
+ InSequence seq;
+
+ ceph::bufferlist data;
+ data.append("X");
+ int object_dispatch_flags = 0;
+ C_SaferCond cond1;
+ Context *on_finish1 = &cond1;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
+ 0, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, nullptr, &on_finish1,
+ nullptr));
+ ASSERT_NE(on_finish1, &cond1);
+
+ Context *timer_task = nullptr;
+ expect_schedule_dispatch_delayed_requests(nullptr, &timer_task);
+
+ uint64_t object_off = 20;
+ data.clear();
+ data.append(std::string(10, 'A'));
+ io::DispatchResult dispatch_result;
+ C_SaferCond cond2;
+ Context *on_finish2 = &cond2;
+ C_SaferCond on_dispatched2;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, object_off, std::move(data), mock_image_ctx.get_data_io_context(), 0,
+ 0, std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish2, &on_dispatched2));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish2, &cond2);
+ ASSERT_NE(timer_task, nullptr);
+
+ object_off = 0;
+ data.clear();
+ data.append(std::string(10, 'B'));
+ C_SaferCond cond3;
+ Context *on_finish3 = &cond3;
+ C_SaferCond on_dispatched3;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, object_off, std::move(data), mock_image_ctx.get_data_io_context(), 0,
+ 0, std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish3, &on_dispatched3));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish3, &cond3);
+
+ object_off = 10;
+ data.clear();
+ data.append(std::string(10, 'C'));
+ C_SaferCond cond4;
+ Context *on_finish4 = &cond4;
+ C_SaferCond on_dispatched4;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, object_off, std::move(data), mock_image_ctx.get_data_io_context(), 0,
+ 0, std::nullopt, {},&object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish4, &on_dispatched4));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish4, &cond4);
+
+ object_off = 30;
+ data.clear();
+ data.append(std::string(10, 'D'));
+ C_SaferCond cond5;
+ Context *on_finish5 = &cond5;
+ C_SaferCond on_dispatched5;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, object_off, std::move(data), mock_image_ctx.get_data_io_context(), 0,
+ 0, std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish5, &on_dispatched5));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish5, &cond5);
+
+ object_off = 50;
+ data.clear();
+ data.append(std::string(10, 'E'));
+ C_SaferCond cond6;
+ Context *on_finish6 = &cond6;
+ C_SaferCond on_dispatched6;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, object_off, std::move(data), mock_image_ctx.get_data_io_context(), 0,
+ 0, std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish6, &on_dispatched6));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish6, &cond6);
+
+ // expect two requests dispatched:
+ // 0~40 (merged 0~10, 10~10, 20~10, 30~10) and 50~10
+ expect_dispatch_delayed_requests(mock_image_ctx, 0);
+ expect_dispatch_delayed_requests(mock_image_ctx, 0);
+ expect_schedule_dispatch_delayed_requests(timer_task, nullptr);
+
+ on_finish1->complete(0);
+ ASSERT_EQ(0, cond1.wait());
+ ASSERT_EQ(0, on_dispatched2.wait());
+ ASSERT_EQ(0, on_dispatched3.wait());
+ ASSERT_EQ(0, on_dispatched4.wait());
+ ASSERT_EQ(0, on_dispatched5.wait());
+ ASSERT_EQ(0, on_dispatched6.wait());
+ on_finish2->complete(0);
+ on_finish3->complete(0);
+ on_finish4->complete(0);
+ on_finish5->complete(0);
+ on_finish6->complete(0);
+ ASSERT_EQ(0, cond2.wait());
+ ASSERT_EQ(0, cond3.wait());
+ ASSERT_EQ(0, cond4.wait());
+ ASSERT_EQ(0, cond5.wait());
+ ASSERT_EQ(0, cond6.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteNonSequential) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ expect_get_object_name(mock_image_ctx, 0);
+
+ InSequence seq;
+
+ ceph::bufferlist data;
+ int object_dispatch_flags = 0;
+ C_SaferCond cond1;
+ Context *on_finish1 = &cond1;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
+ 0, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, nullptr, &on_finish1,
+ nullptr));
+ ASSERT_NE(on_finish1, &cond1);
+
+ Context *timer_task = nullptr;
+ expect_schedule_dispatch_delayed_requests(nullptr, &timer_task);
+
+ uint64_t object_off = 0;
+ data.clear();
+ data.append(std::string(10, 'X'));
+ io::DispatchResult dispatch_result;
+ C_SaferCond cond2;
+ Context *on_finish2 = &cond2;
+ C_SaferCond on_dispatched2;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, object_off, std::move(data), mock_image_ctx.get_data_io_context(), 0,
+ 0, std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish2, &on_dispatched2));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish2, &cond2);
+ ASSERT_NE(timer_task, nullptr);
+
+ expect_dispatch_delayed_requests(mock_image_ctx, 0);
+ expect_schedule_dispatch_delayed_requests(timer_task, nullptr);
+
+ object_off = 5;
+ data.clear();
+ data.append(std::string(10, 'Y'));
+ C_SaferCond cond3;
+ Context *on_finish3 = &cond3;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
+ 0, object_off, std::move(data), mock_image_ctx.get_data_io_context(), 0,
+ 0, std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish3, nullptr));
+ ASSERT_NE(on_finish3, &cond3);
+
+ on_finish1->complete(0);
+ ASSERT_EQ(0, cond1.wait());
+ ASSERT_EQ(0, on_dispatched2.wait());
+ on_finish2->complete(0);
+ ASSERT_EQ(0, cond2.wait());
+ on_finish3->complete(0);
+ ASSERT_EQ(0, cond3.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Mixed) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ expect_get_object_name(mock_image_ctx, 0);
+
+ InSequence seq;
+
+ // write (1) 0~0 (in-flight)
+ // will wrap on_finish with dispatch_seq=1 to dispatch future delayed writes
+ ceph::bufferlist data;
+ int object_dispatch_flags = 0;
+ C_SaferCond cond1;
+ Context *on_finish1 = &cond1;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
+ 0, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, nullptr, &on_finish1,
+ nullptr));
+ ASSERT_NE(on_finish1, &cond1);
+
+ // write (2) 0~10 (delayed)
+ // will wait for write (1) to finish or a non-seq io comes
+ Context *timer_task = nullptr;
+ expect_schedule_dispatch_delayed_requests(nullptr, &timer_task);
+ uint64_t object_off = 0;
+ data.clear();
+ data.append(std::string(10, 'A'));
+ io::DispatchResult dispatch_result;
+ C_SaferCond cond2;
+ Context *on_finish2 = &cond2;
+ C_SaferCond on_dispatched2;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, object_off, std::move(data), mock_image_ctx.get_data_io_context(), 0,
+ 0, std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish2, &on_dispatched2));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish2, &cond2);
+ ASSERT_NE(timer_task, nullptr);
+
+ // write (3) 10~10 (delayed)
+ // will be merged with write (2)
+ object_off = 10;
+ data.clear();
+ data.append(std::string(10, 'B'));
+ C_SaferCond cond3;
+ Context *on_finish3 = &cond3;
+ C_SaferCond on_dispatched3;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, object_off, std::move(data), mock_image_ctx.get_data_io_context(), 0,
+ 0, std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish3, &on_dispatched3));
+ ASSERT_NE(on_finish3, &cond3);
+
+ // discard (1) (non-seq io)
+ // will dispatch the delayed writes (2) and (3) and wrap on_finish
+ // with dispatch_seq=2 to dispatch future delayed writes
+ expect_dispatch_delayed_requests(mock_image_ctx, 0);
+ expect_schedule_dispatch_delayed_requests(timer_task, nullptr);
+ C_SaferCond cond4;
+ Context *on_finish4 = &cond4;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.discard(
+ 0, 4096, 4096, mock_image_ctx.get_data_io_context(), 0, {}, nullptr,
+ nullptr, nullptr, &on_finish4, nullptr));
+ ASSERT_NE(on_finish4, &cond4);
+ ASSERT_EQ(0, on_dispatched2.wait());
+ ASSERT_EQ(0, on_dispatched3.wait());
+
+ // write (4) 20~10 (delayed)
+ // will wait for discard (1) to finish or a non-seq io comes
+ timer_task = nullptr;
+ expect_schedule_dispatch_delayed_requests(nullptr, &timer_task);
+ object_off = 20;
+ data.clear();
+ data.append(std::string(10, 'C'));
+ C_SaferCond cond5;
+ Context *on_finish5 = &cond5;
+ C_SaferCond on_dispatched5;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, object_off, std::move(data), mock_image_ctx.get_data_io_context(), 0,
+ 0, std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish5, &on_dispatched5));
+ ASSERT_NE(on_finish5, &cond5);
+ ASSERT_NE(timer_task, nullptr);
+
+ // discard (2) (non-seq io)
+ // will dispatch the delayed write (4) and wrap on_finish with dispatch_seq=3
+ // to dispatch future delayed writes
+ expect_dispatch_delayed_requests(mock_image_ctx, 0);
+ expect_schedule_dispatch_delayed_requests(timer_task, nullptr);
+ C_SaferCond cond6;
+ Context *on_finish6 = &cond6;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.discard(
+ 0, 4096, 4096, mock_image_ctx.get_data_io_context(), 0, {}, nullptr,
+ nullptr, nullptr, &on_finish6, nullptr));
+ ASSERT_NE(on_finish6, &cond6);
+ ASSERT_EQ(0, on_dispatched5.wait());
+
+ // write (5) 30~10 (delayed)
+ // will wait for discard (2) to finish or a non-seq io comes
+ timer_task = nullptr;
+ expect_schedule_dispatch_delayed_requests(nullptr, &timer_task);
+ object_off = 30;
+ data.clear();
+ data.append(std::string(10, 'D'));
+ C_SaferCond cond7;
+ Context *on_finish7 = &cond7;
+ C_SaferCond on_dispatched7;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, object_off, std::move(data), mock_image_ctx.get_data_io_context(), 0,
+ 0, std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish7, &on_dispatched7));
+ ASSERT_NE(on_finish7, &cond7);
+ ASSERT_NE(timer_task, nullptr);
+
+ // write (1) finishes
+ // on_finish wrapper will skip dispatch delayed write (5)
+ // due to dispatch_seq(1) < m_dispatch_seq(3)
+ on_finish1->complete(0);
+ ASSERT_EQ(0, cond1.wait());
+
+ // writes (2) and (3) finish ("dispatch delayed" is not called)
+ on_finish2->complete(0);
+ on_finish3->complete(0);
+ ASSERT_EQ(0, cond2.wait());
+ ASSERT_EQ(0, cond3.wait());
+
+ // discard (1) finishes
+ // on_finish wrapper will skip dispatch delayed write (5)
+ // due to dispatch_seq(2) < m_dispatch_seq(3)
+ on_finish4->complete(0);
+ ASSERT_EQ(0, cond4.wait());
+
+ // writes (4) finishes ("dispatch delayed" is not called)
+ on_finish5->complete(0);
+ ASSERT_EQ(0, cond5.wait());
+
+ // discard (2) finishes
+ // on_finish wrapper will dispatch the delayed write (5)
+ // due to dispatch_seq(3) == m_dispatch_seq(3)
+ expect_dispatch_delayed_requests(mock_image_ctx, 0);
+ expect_schedule_dispatch_delayed_requests(timer_task, nullptr);
+ on_finish6->complete(0);
+ ASSERT_EQ(0, cond6.wait());
+ ASSERT_EQ(0, on_dispatched7.wait());
+
+ // write (5) finishes ("dispatch delayed" is not called)
+ on_finish7->complete(0);
+ ASSERT_EQ(0, cond7.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, DispatchQueue) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ expect_get_object_name(mock_image_ctx, 0);
+ expect_get_object_name(mock_image_ctx, 1);
+
+ InSequence seq;
+
+ // send 2 writes to object 0
+
+ uint64_t object_no = 0;
+ ceph::bufferlist data;
+ int object_dispatch_flags = 0;
+ C_SaferCond cond1;
+ Context *on_finish1 = &cond1;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
+ object_no, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, nullptr, &on_finish1,
+ nullptr));
+ ASSERT_NE(on_finish1, &cond1);
+
+ Context *timer_task = nullptr;
+ expect_schedule_dispatch_delayed_requests(nullptr, &timer_task);
+
+ data.clear();
+ io::DispatchResult dispatch_result;
+ C_SaferCond cond2;
+ Context *on_finish2 = &cond2;
+ C_SaferCond on_dispatched2;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ object_no, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish2, &on_dispatched2));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish2, &cond2);
+ ASSERT_NE(timer_task, nullptr);
+
+ // send 2 writes to object 1
+
+ object_no = 1;
+ data.clear();
+ C_SaferCond cond3;
+ Context *on_finish3 = &cond3;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
+ object_no, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, nullptr, &on_finish3,
+ nullptr));
+ ASSERT_NE(on_finish3, &cond3);
+
+ data.clear();
+ C_SaferCond cond4;
+ Context *on_finish4 = &cond4;
+ C_SaferCond on_dispatched4;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ object_no, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish4, &on_dispatched4));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish4, &cond4);
+
+ // finish write (1) to object 0
+ expect_dispatch_delayed_requests(mock_image_ctx, 0);
+ expect_schedule_dispatch_delayed_requests(timer_task, &timer_task);
+ on_finish1->complete(0);
+ ASSERT_EQ(0, cond1.wait());
+ ASSERT_EQ(0, on_dispatched2.wait());
+
+ // finish write (2) to object 0
+ on_finish2->complete(0);
+ ASSERT_EQ(0, cond2.wait());
+
+ // finish write (1) to object 1
+ expect_dispatch_delayed_requests(mock_image_ctx, 0);
+ expect_schedule_dispatch_delayed_requests(timer_task, nullptr);
+ on_finish3->complete(0);
+ ASSERT_EQ(0, cond3.wait());
+ ASSERT_EQ(0, on_dispatched4.wait());
+
+ // finish write (2) to object 1
+ on_finish4->complete(0);
+ ASSERT_EQ(0, cond4.wait());
+}
+
+TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Timer) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSimpleSchedulerObjectDispatch
+ mock_simple_scheduler_object_dispatch(&mock_image_ctx);
+
+ expect_get_object_name(mock_image_ctx, 0);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ ceph::bufferlist data;
+ int object_dispatch_flags = 0;
+ C_SaferCond cond1;
+ Context *on_finish1 = &cond1;
+ ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
+ 0, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, nullptr, &on_finish1,
+ nullptr));
+ ASSERT_NE(on_finish1, &cond1);
+
+ Context *timer_task = nullptr;
+ expect_schedule_dispatch_delayed_requests(nullptr, &timer_task);
+
+ data.clear();
+ io::DispatchResult dispatch_result;
+ C_SaferCond cond2;
+ Context *on_finish2 = &cond2;
+ C_SaferCond on_dispatched;
+ ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
+ 0, 0, std::move(data), mock_image_ctx.get_data_io_context(), 0, 0,
+ std::nullopt, {}, &object_dispatch_flags, nullptr, &dispatch_result,
+ &on_finish2, &on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_NE(on_finish2, &cond2);
+ ASSERT_NE(timer_task, nullptr);
+
+ expect_dispatch_delayed_requests(mock_image_ctx, 0);
+
+ run_timer_task(timer_task);
+ ASSERT_EQ(0, on_dispatched.wait());
+
+ on_finish1->complete(0);
+ ASSERT_EQ(0, cond1.wait());
+ on_finish2->complete(0);
+ ASSERT_EQ(0, cond2.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 000000000..d89c4f8e4
--- /dev/null
+++ b/src/test/librbd/journal/test_Entries.cc
@@ -0,0 +1,227 @@
+// -*- 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/api/Io.h"
+#include "librbd/io/AioCompletion.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() {
+}
+
+namespace librbd {
+namespace journal {
+
+class TestJournalEntries : public TestFixture {
+public:
+ typedef std::list<::journal::Journaler *> Journalers;
+
+ struct ReplayHandler : public ::journal::ReplayHandler {
+ ceph::mutex lock = ceph::make_mutex("ReplayHandler::lock");
+ ceph::condition_variable cond;
+ bool entries_available;
+ bool complete;
+
+ ReplayHandler()
+ : entries_available(false), complete(false) {
+ }
+
+ void handle_entries_available() override {
+ std::lock_guard locker{lock};
+ entries_available = true;
+ cond.notify_all();
+ }
+
+ void handle_complete(int r) override {
+ std::lock_guard locker{lock};
+ complete = true;
+ cond.notify_all();
+ }
+ };
+
+ 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", {}, nullptr);
+
+ 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) {
+ std::unique_lock locker{m_replay_handler.lock};
+ while (!m_replay_handler.entries_available) {
+ if (m_replay_handler.cond.wait_for(locker, 10s) == std::cv_status::timeout) {
+ 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();
+ api::Io<>::aio_write(*ictx, c, 123, buffer.size(), std::move(write_bl), 0,
+ true);
+ 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();
+ api::Io<>::aio_discard(*ictx, c, 123, 234, ictx->discard_granularity_bytes,
+ true);
+ 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();
+ api::Io<>::aio_flush(*ictx, c, true);
+ 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());
+}
+
+} // namespace journal
+} // namespace librbd
diff --git a/src/test/librbd/journal/test_Replay.cc b/src/test/librbd/journal/test_Replay.cc
new file mode 100644
index 000000000..9b4580e64
--- /dev/null
+++ b/src/test/librbd/journal/test_Replay.cc
@@ -0,0 +1,886 @@
+// -*- 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/api/Io.h"
+#include "librbd/api/Snapshot.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ImageDispatchSpec.h"
+#include "librbd/io/ImageRequest.h"
+#include "librbd/io/ReadResult.h"
+#include "librbd/journal/Types.h"
+
+void register_test_journal_replay() {
+}
+
+namespace librbd {
+namespace journal {
+
+class TestJournalReplay : public TestFixture {
+public:
+
+ int when_acquired_lock(librbd::ImageCtx *ictx) {
+ C_SaferCond lock_ctx;
+ {
+ std::unique_lock 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);
+ {
+ std::shared_lock 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());
+
+ ictx->journal->put();
+ 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();
+ api::Io<>::aio_write(*ictx, aio_comp, 0, payload.size(),
+ std::move(payload_bl), 0, true);
+ ASSERT_EQ(0, aio_comp->wait_for_complete());
+ aio_comp->release();
+
+ aio_comp = new librbd::io::AioCompletion();
+ api::Io<>::aio_flush(*ictx, aio_comp, true);
+ 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();
+ api::Io<>::aio_read(*ictx, aio_comp, 0, read_payload.size(),
+ librbd::io::ReadResult{read_result}, 0, true);
+ 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();
+ api::Io<>::aio_read(*ictx, aio_comp, 0, read_payload.size(),
+ librbd::io::ReadResult{read_result}, 0, true);
+ 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();
+ api::Io<>::aio_discard(*ictx, aio_comp, 0, read_payload.size(),
+ ictx->discard_granularity_bytes, true);
+ 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();
+ api::Io<>::aio_read(*ictx, aio_comp, 0, read_payload.size(),
+ std::move(read_result), 0, true);
+ 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();
+ api::Io<>::aio_write(*ictx, aio_comp, 0, payload.size(),
+ bufferlist{payload_bl}, 0, true);
+ 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();
+ api::Io<>::aio_flush(*ictx, aio_comp, true);
+ 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);
+
+ {
+ std::shared_lock image_locker{ictx->image_lock};
+ ASSERT_NE(CEPH_NOSNAP, ictx->get_snap_id(cls::rbd::UserSnapshotNamespace(),
+ "snap"));
+ }
+
+ // verify lock ordering constraints
+ librbd::NoOpProgressContext no_op_progress;
+ ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(),
+ "snap2", 0, no_op_progress));
+}
+
+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));
+
+ librbd::NoOpProgressContext no_op_progress;
+ ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(),
+ "snap", 0, no_op_progress));
+
+ // 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::api::Snapshot<>::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", 0, no_op_progress));
+ 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));
+
+ librbd::NoOpProgressContext no_op_progress;
+ ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(),
+ "snap", 0, no_op_progress));
+ uint64_t snap_id;
+ {
+ std::shared_lock image_locker{ictx->image_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::api::Snapshot<>::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", 0, no_op_progress));
+ 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));
+
+ librbd::NoOpProgressContext no_op_progress;
+ ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(),
+ "snap", 0, no_op_progress));
+ uint64_t snap_id;
+ {
+ std::shared_lock image_locker{ictx->image_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());
+
+ {
+ std::shared_lock image_locker{ictx->image_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));
+
+ librbd::NoOpProgressContext no_op_progress;
+ ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(),
+ "snap", 0, no_op_progress));
+
+ // 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
+ 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));
+
+ librbd::NoOpProgressContext no_op_progress;
+ ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(),
+ "snap", 0, no_op_progress));
+
+ // 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);
+
+ {
+ std::shared_lock image_locker{ictx->image_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", 0, no_op_progress));
+ 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));
+ librbd::NoOpProgressContext no_op_progress;
+ ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(),
+ "snap", 0, no_op_progress));
+ 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();
+ api::Io<>::aio_write(*ictx, aio_comp, 0, payload.size(),
+ bufferlist{payload_bl}, 0, true);
+ ASSERT_EQ(0, aio_comp->wait_for_complete());
+ aio_comp->release();
+
+ aio_comp = new librbd::io::AioCompletion();
+ api::Io<>::aio_flush(*ictx, aio_comp, true);
+ 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();
+ api::Io<>::aio_write(*ictx, aio_comp, 0, payload.size(),
+ bufferlist{payload_bl}, 0, true);
+ ASSERT_EQ(0, aio_comp->wait_for_complete());
+ aio_comp->release();
+
+ aio_comp = new librbd::io::AioCompletion();
+ api::Io<>::aio_flush(*ictx, aio_comp, true);
+ 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(
+ *ictx, librbd::io::IMAGE_DISPATCH_LAYER_INTERNAL_START, aio_comp,
+ librbd::io::FLUSH_SOURCE_INTERNAL, {});
+ req->send();
+ 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);
+}
+
+} // namespace journal
+} // namespace librbd
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 000000000..0d86f20e9
--- /dev/null
+++ b/src/test/librbd/journal/test_mock_OpenRequest.cc
@@ -0,0 +1,193 @@
+// -*- 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/ceph_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() = default;
+
+ void expect_init_journaler(::journal::MockJournaler &mock_journaler, int r) {
+ EXPECT_CALL(mock_journaler, init(_))
+ .WillOnce(CompleteContext(r, static_cast<asio::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))));
+ }
+
+ ceph::mutex m_lock = ceph::make_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 000000000..c4e7ed9dc
--- /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,
+ ceph::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<asio::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<asio::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<asio::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<asio::ContextWQ*>(NULL)));
+ }
+
+ void expect_shut_down_journaler(::journal::MockJournaler &mock_journaler,
+ int r) {
+ EXPECT_CALL(mock_journaler, shut_down(_))
+ .WillOnce(CompleteContext(r, static_cast<asio::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 000000000..b902e0f95
--- /dev/null
+++ b/src/test/librbd/journal/test_mock_Replay.cc
@@ -0,0 +1,2042 @@
+// -*- 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,
+ IOContext io_context, 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,
+ IOContext io_context,
+ 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,
+ IOContext io_context, 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,
+ IOContext io_context, 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) {
+ std::lock_guard locker{*lock};
+ cond->notify_all();
+}
+
+ACTION_P2(CompleteAioCompletion, r, image_ctx) {
+ image_ctx->op_work_queue->queue(new LambdaContext([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() = default;
+
+ 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,
+ SNAP_CREATE_FLAG_SKIP_NOTIFY_QUIESCE, _))
+ .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 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) {
+ {
+ std::unique_lock locker{m_invoke_lock};
+ m_invoke_cond.wait(locker, [on_finish] { return *on_finish != nullptr; });
+ }
+ (*on_finish)->complete(r);
+ }
+
+ bufferlist to_bl(const std::string &str) {
+ bufferlist bl;
+ bl.append(str);
+ return bl;
+ }
+
+ ceph::mutex m_invoke_lock = ceph::make_mutex("m_invoke_lock");
+ ceph::condition_variable 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_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_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_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_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_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_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_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_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);
+ ictx->op_work_queue->drain();
+ 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);
+ ictx->op_work_queue->drain();
+
+ 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);
+ ictx->op_work_queue->drain();
+ 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);
+ ictx->op_work_queue->drain();
+
+ 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());
+}
+
+} // 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 000000000..2c722558c
--- /dev/null
+++ b/src/test/librbd/journal/test_mock_ResetRequest.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 "test/journal/mock/MockJournaler.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);
+
+ ContextWQ* context_wq;
+ Journal<>::get_work_queue(ictx->cct, &context_wq);
+
+ C_SaferCond ctx;
+ auto req = MockResetRequest::create(m_ioctx, "image id",
+ Journal<>::IMAGE_CLIENT_ID,
+ Journal<>::LOCAL_MIRROR_UUID,
+ context_wq, &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);
+
+ ContextWQ* context_wq;
+ Journal<>::get_work_queue(ictx->cct, &context_wq);
+
+ C_SaferCond ctx;
+ auto req = MockResetRequest::create(m_ioctx, "image id",
+ Journal<>::IMAGE_CLIENT_ID,
+ Journal<>::LOCAL_MIRROR_UUID,
+ context_wq, &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);
+
+ ContextWQ* context_wq;
+ Journal<>::get_work_queue(ictx->cct, &context_wq);
+
+ C_SaferCond ctx;
+ auto req = MockResetRequest::create(m_ioctx, "image id",
+ Journal<>::IMAGE_CLIENT_ID,
+ Journal<>::LOCAL_MIRROR_UUID,
+ context_wq, &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);
+
+ ContextWQ* context_wq;
+ Journal<>::get_work_queue(ictx->cct, &context_wq);
+
+ C_SaferCond ctx;
+ auto req = MockResetRequest::create(m_ioctx, "image id",
+ Journal<>::IMAGE_CLIENT_ID,
+ Journal<>::LOCAL_MIRROR_UUID,
+ context_wq, &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);
+
+ ContextWQ* context_wq;
+ Journal<>::get_work_queue(ictx->cct, &context_wq);
+
+ C_SaferCond ctx;
+ auto req = MockResetRequest::create(m_ioctx, "image id",
+ Journal<>::IMAGE_CLIENT_ID,
+ Journal<>::LOCAL_MIRROR_UUID,
+ context_wq, &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 000000000..f3e587c92
--- /dev/null
+++ b/src/test/librbd/managed_lock/test_mock_AcquireRequest.cc
@@ -0,0 +1,271 @@
+// -*- 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,
+ AsioEngine& asio_engine,
+ const std::string& oid, const Locker &locker,
+ bool exclusive, bool blocklist_locker,
+ uint32_t blocklist_expire_seconds,
+ bool force_break_lock, Context *on_finish) {
+ CephContext *cct = reinterpret_cast<CephContext *>(ioctx.cct());
+ EXPECT_EQ(cct->_conf.get_val<bool>("rbd_blocklist_on_break_lock"),
+ blocklist_locker);
+ EXPECT_EQ(cct->_conf.get_val<uint64_t>("rbd_blocklist_expire_seconds"),
+ blocklist_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 ? ClsLockType::EXCLUSIVE : ClsLockType::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->asio_engine, 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->asio_engine, 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->asio_engine, 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->asio_engine, 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->asio_engine, 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->asio_engine, 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 000000000..d6239344b
--- /dev/null
+++ b/src/test/librbd/managed_lock/test_mock_BreakRequest.cc
@@ -0,0 +1,484 @@
+// -*- 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;
+ strncpy(watcher.addr, (address + ":0/0").c_str(), sizeof(watcher.addr) - 1);
+ watcher.addr[sizeof(watcher.addr) - 1] = '\0';
+ 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_blocklist_add(MockTestImageCtx &mock_image_ctx, int r) {
+ auto& mock_rados_client = librados::get_mock_rados_client(
+ mock_image_ctx.rados_api);
+ EXPECT_CALL(mock_rados_client, blocklist_add(_, _)).WillOnce(Return(r));
+ }
+
+ void expect_wait_for_latest_osd_map(MockTestImageCtx &mock_image_ctx, int r) {
+ auto& mock_rados_client = librados::get_mock_rados_client(
+ mock_image_ctx.rados_api);
+ EXPECT_CALL(mock_rados_client, wait_for_latest_osd_map())
+ .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_blocklist_add(mock_image_ctx, 0);
+ expect_wait_for_latest_osd_map(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->asio_engine, 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_blocklist_add(mock_image_ctx, 0);
+ expect_wait_for_latest_osd_map(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->asio_engine, 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->asio_engine, 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->asio_engine, 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->asio_engine, 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->asio_engine, 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->asio_engine, 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->asio_engine, mock_image_ctx.header_oid,
+ locker, true, false, 0, false, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockManagedLockBreakRequest, BlocklistDisabled) {
+ 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->asio_engine, mock_image_ctx.header_oid,
+ locker, true, false, 0, false, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockManagedLockBreakRequest, BlocklistSelf) {
+ 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->asio_engine, mock_image_ctx.header_oid,
+ locker, true, true, 0, false, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockManagedLockBreakRequest, BlocklistError) {
+ 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_blocklist_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->asio_engine, 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_blocklist_add(mock_image_ctx, 0);
+ expect_wait_for_latest_osd_map(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->asio_engine, 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_blocklist_add(mock_image_ctx, 0);
+ expect_wait_for_latest_osd_map(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->asio_engine, 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 000000000..8cad1f092
--- /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(),
+ ClsLockType::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(),
+ ClsLockType::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), "",
+ "", "", ClsLockType::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), "",
+ "", "", ClsLockType::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", ClsLockType::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(),
+ ClsLockType::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(),
+ ClsLockType::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(),
+ ClsLockType::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 000000000..ec63925ac
--- /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 ? ClsLockType::EXCLUSIVE : ClsLockType::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 000000000..63752475e
--- /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/migration/test_mock_FileStream.cc b/src/test/librbd/migration/test_mock_FileStream.cc
new file mode 100644
index 000000000..3688b56dd
--- /dev/null
+++ b/src/test/librbd/migration/test_mock_FileStream.cc
@@ -0,0 +1,217 @@
+// -*- 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/migration/FileStream.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "json_spirit/json_spirit.h"
+#if __has_include(<filesystem>)
+#include <filesystem>
+namespace fs = std::filesystem;
+#else
+#include <experimental/filesystem>
+namespace fs = std::experimental::filesystem;
+#endif
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+#include "librbd/migration/FileStream.cc"
+
+namespace librbd {
+namespace migration {
+
+using ::testing::Invoke;
+
+class TestMockMigrationFileStream : public TestMockFixture {
+public:
+ typedef FileStream<MockTestImageCtx> MockFileStream;
+
+ librbd::ImageCtx *m_image_ctx;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+ file_name = fs::temp_directory_path() / "TestMockMigrationFileStream";
+ file_name += stringify(getpid());
+ json_object["file_path"] = file_name;
+ }
+
+ void TearDown() override {
+ fs::remove(file_name);
+ TestMockFixture::TearDown();
+ }
+
+ std::string file_name;
+ json_spirit::mObject json_object;
+};
+
+TEST_F(TestMockMigrationFileStream, OpenClose) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ bufferlist bl;
+ ASSERT_EQ(0, bl.write_file(file_name.c_str()));
+
+ MockFileStream mock_file_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_file_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_file_stream.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationFileStream, GetSize) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(128, '1'));
+ ASSERT_EQ(0, expect_bl.write_file(file_name.c_str()));
+
+ MockFileStream mock_file_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_file_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ uint64_t size;
+ mock_file_stream.get_size(&size, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(128, size);
+
+ C_SaferCond ctx3;
+ mock_file_stream.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationFileStream, Read) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(128, '1'));
+ ASSERT_EQ(0, expect_bl.write_file(file_name.c_str()));
+
+ MockFileStream mock_file_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_file_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ bufferlist bl;
+ mock_file_stream.read({{0, 128}}, &bl, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ mock_file_stream.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationFileStream, SeekRead) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ bufferlist write_bl;
+ write_bl.append(std::string(32, '1'));
+ write_bl.append(std::string(64, '2'));
+ write_bl.append(std::string(16, '3'));
+ ASSERT_EQ(0, write_bl.write_file(file_name.c_str()));
+
+ MockFileStream mock_file_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_file_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ bufferlist bl;
+ mock_file_stream.read({{96, 16}, {32, 64}, {0, 32}}, &bl, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(16, '3'));
+ expect_bl.append(std::string(64, '2'));
+ expect_bl.append(std::string(32, '1'));
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ mock_file_stream.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationFileStream, DNE) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ MockFileStream mock_file_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_file_stream.open(&ctx1);
+ ASSERT_EQ(-ENOENT, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_file_stream.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationFileStream, SeekError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ bufferlist bl;
+ ASSERT_EQ(0, bl.write_file(file_name.c_str()));
+
+ MockFileStream mock_file_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_file_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_file_stream.read({{128, 128}}, &bl, &ctx2);
+ ASSERT_EQ(-ERANGE, ctx2.wait());
+
+ C_SaferCond ctx3;
+ mock_file_stream.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationFileStream, ShortReadError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(128, '1'));
+ ASSERT_EQ(0, expect_bl.write_file(file_name.c_str()));
+
+ MockFileStream mock_file_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_file_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ bufferlist bl;
+ mock_file_stream.read({{0, 256}}, &bl, &ctx2);
+ ASSERT_EQ(-ERANGE, ctx2.wait());
+
+ C_SaferCond ctx3;
+ mock_file_stream.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+} // namespace migration
+} // namespace librbd
diff --git a/src/test/librbd/migration/test_mock_HttpClient.cc b/src/test/librbd/migration/test_mock_HttpClient.cc
new file mode 100644
index 000000000..f3888755c
--- /dev/null
+++ b/src/test/librbd/migration/test_mock_HttpClient.cc
@@ -0,0 +1,890 @@
+// -*- 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/migration/HttpClient.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include <unistd.h>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/beast/core.hpp>
+#include <boost/beast/http.hpp>
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockTestImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+#include "librbd/migration/HttpClient.cc"
+
+using EmptyHttpRequest = boost::beast::http::request<
+ boost::beast::http::empty_body>;
+using HttpResponse = boost::beast::http::response<
+ boost::beast::http::string_body>;
+
+namespace boost {
+namespace beast {
+namespace http {
+
+template <typename Body>
+bool operator==(const boost::beast::http::request<Body>& lhs,
+ const boost::beast::http::request<Body>& rhs) {
+ return (lhs.method() == rhs.method() &&
+ lhs.target() == rhs.target());
+}
+
+template <typename Body>
+bool operator==(const boost::beast::http::response<Body>& lhs,
+ const boost::beast::http::response<Body>& rhs) {
+ return (lhs.result() == rhs.result() &&
+ lhs.body() == rhs.body());
+}
+
+} // namespace http
+} // namespace beast
+} // namespace boost
+
+namespace librbd {
+namespace migration {
+
+using ::testing::Invoke;
+
+class TestMockMigrationHttpClient : public TestMockFixture {
+public:
+ typedef HttpClient<MockTestImageCtx> MockHttpClient;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+
+ create_acceptor(false);
+ }
+
+ void TearDown() override {
+ m_acceptor.reset();
+
+ TestMockFixture::TearDown();
+ }
+
+ // if we have a racing where another thread manages to bind and listen the
+ // port picked by this acceptor, try again.
+ static constexpr int MAX_BIND_RETRIES = 60;
+
+ void create_acceptor(bool reuse) {
+ for (int retries = 0;; retries++) {
+ try {
+ m_acceptor.emplace(*m_image_ctx->asio_engine,
+ boost::asio::ip::tcp::endpoint(
+ boost::asio::ip::tcp::v4(), m_server_port), reuse);
+ // yay!
+ break;
+ } catch (const boost::system::system_error& e) {
+ if (retries == MAX_BIND_RETRIES) {
+ throw;
+ }
+ if (e.code() != boost::system::errc::address_in_use) {
+ throw;
+ }
+ }
+ // backoff a little bit
+ sleep(1);
+ }
+ m_server_port = m_acceptor->local_endpoint().port();
+ }
+
+ std::string get_local_url(UrlScheme url_scheme) {
+ std::stringstream sstream;
+ switch (url_scheme) {
+ case URL_SCHEME_HTTP:
+ sstream << "http://127.0.0.1";
+ break;
+ case URL_SCHEME_HTTPS:
+ sstream << "https://localhost";
+ break;
+ default:
+ ceph_assert(false);
+ break;
+ }
+
+ sstream << ":" << m_server_port << "/target";
+ return sstream.str();
+ }
+
+ void client_accept(boost::asio::ip::tcp::socket* socket, bool close,
+ Context* on_connect) {
+ m_acceptor->async_accept(
+ boost::asio::make_strand(m_image_ctx->asio_engine->get_executor()),
+ [socket, close, on_connect]
+ (auto ec, boost::asio::ip::tcp::socket in_socket) {
+ if (close) {
+ in_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
+ } else {
+ ASSERT_FALSE(ec) << "Unexpected error: " << ec;
+ *socket = std::move(in_socket);
+ }
+ on_connect->complete(0);
+ });
+ }
+
+ template <typename Body>
+ void client_read_request(boost::asio::ip::tcp::socket& socket,
+ boost::beast::http::request<Body>& expected_req) {
+ boost::beast::http::request<Body> req;
+ boost::beast::error_code ec;
+ boost::beast::http::read(socket, m_buffer, req, ec);
+ ASSERT_FALSE(ec) << "Unexpected errror: " << ec;
+
+ expected_req.target("/target");
+ ASSERT_EQ(expected_req, req);
+ }
+
+ void client_write_response(boost::asio::ip::tcp::socket& socket,
+ HttpResponse& expected_res) {
+ expected_res.set(boost::beast::http::field::server,
+ BOOST_BEAST_VERSION_STRING);
+ expected_res.set(boost::beast::http::field::content_type, "text/plain");
+ expected_res.content_length(expected_res.body().size());
+ expected_res.prepare_payload();
+
+ boost::beast::error_code ec;
+ boost::beast::http::write(socket, expected_res, ec);
+ ASSERT_FALSE(ec) << "Unexpected errror: " << ec;
+ }
+
+ template <typename Stream>
+ void client_ssl_handshake(Stream& stream, bool ignore_failure,
+ Context* on_handshake) {
+ stream.async_handshake(
+ boost::asio::ssl::stream_base::server,
+ [ignore_failure, on_handshake](auto ec) {
+ ASSERT_FALSE(!ignore_failure && ec) << "Unexpected error: " << ec;
+ on_handshake->complete(-ec.value());
+ });
+ }
+
+ template <typename Stream>
+ void client_ssl_shutdown(Stream& stream, Context* on_shutdown) {
+ stream.async_shutdown(
+ [on_shutdown](auto ec) {
+ ASSERT_FALSE(ec) << "Unexpected error: " << ec;
+ on_shutdown->complete(-ec.value());
+ });
+ }
+
+ void load_server_certificate(boost::asio::ssl::context& ctx) {
+ ctx.set_options(
+ boost::asio::ssl::context::default_workarounds |
+ boost::asio::ssl::context::no_sslv2 |
+ boost::asio::ssl::context::single_dh_use);
+ ctx.use_certificate_chain(
+ boost::asio::buffer(CERTIFICATE.data(), CERTIFICATE.size()));
+ ctx.use_private_key(
+ boost::asio::buffer(KEY.data(), KEY.size()),
+ boost::asio::ssl::context::file_format::pem);
+ ctx.use_tmp_dh(
+ boost::asio::buffer(DH.data(), DH.size()));
+ }
+
+ // dummy self-signed cert for localhost
+ const std::string CERTIFICATE =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIDXzCCAkegAwIBAgIUYH6rAaq66LC6yJ3XK1WEMIfmY4cwDQYJKoZIhvcNAQEL\n"
+ "BQAwPzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMQ8wDQYDVQQHDAZNY0xlYW4x\n"
+ "EjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMDExMDIyMTM5NTVaFw00ODAzMjAyMTM5\n"
+ "NTVaMD8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJWQTEPMA0GA1UEBwwGTWNMZWFu\n"
+ "MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
+ "AoIBAQCeRkyxjP0eNHxzj4/R+Bg/31p7kEjB5d/LYtrzBIYNe+3DN8gdReixEpR5\n"
+ "lgTLDsl8gfk2HRz4cnAiseqYL6GKtw/cFadzLyXTbW4iavmTWiGYw/8RJlKunbhA\n"
+ "hDjM6H99ysLf0NS6t14eK+bEJIW1PiTYRR1U5I4kSIjpCX7+nJVuwMEZ2XBpN3og\n"
+ "nHhv2hZYTdzEkQEyZHz4V/ApfD7rlja5ecd/vJfPJeA8nudnGCh3Uo6f8I9TObAj\n"
+ "8hJdfRiRBvnA4NnkrMrxW9UtVjScnw9Xia11FM/IGJIgMpLQ5dqBw930p6FxMYtn\n"
+ "tRD1AF9sT+YjoCaHv0hXZvBEUEF3AgMBAAGjUzBRMB0GA1UdDgQWBBTQoIiX3+p/\n"
+ "P4Xz2vwERz6pbjPGhzAfBgNVHSMEGDAWgBTQoIiX3+p/P4Xz2vwERz6pbjPGhzAP\n"
+ "BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCVKoYAw+D1qqWRDSh3\n"
+ "2KlKMnT6sySo7XmReGArj8FTKiZUprByj5CfAtaiDSdPOpcg3EazWbdasZbMmSQm\n"
+ "+jpe5WoKnxL9b12lwwUYHrLl6RlrDHVkIVlXLNbJFY5TpfjvZfHpwVAygF3fnbgW\n"
+ "PPuODUNAS5NDwST+t29jBZ/wwU0pyW0CS4K5d3XMGHBc13j2V/FyvmsZ5xfA4U9H\n"
+ "oEnmZ/Qm+FFK/nR40rTAZ37cuv4ysKFtwvatNgTfHGJwaBUkKFdDbcyxt9abCi6x\n"
+ "/K+ScoJtdIeVcfx8Fnc5PNtSpy8bHI3Zy4IEyw4kOqwwI1h37iBafZ2WdQkTxlAx\n"
+ "JIDj\n"
+ "-----END CERTIFICATE-----\n";
+ const std::string KEY =
+ "-----BEGIN PRIVATE KEY-----\n"
+ "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCeRkyxjP0eNHxz\n"
+ "j4/R+Bg/31p7kEjB5d/LYtrzBIYNe+3DN8gdReixEpR5lgTLDsl8gfk2HRz4cnAi\n"
+ "seqYL6GKtw/cFadzLyXTbW4iavmTWiGYw/8RJlKunbhAhDjM6H99ysLf0NS6t14e\n"
+ "K+bEJIW1PiTYRR1U5I4kSIjpCX7+nJVuwMEZ2XBpN3ognHhv2hZYTdzEkQEyZHz4\n"
+ "V/ApfD7rlja5ecd/vJfPJeA8nudnGCh3Uo6f8I9TObAj8hJdfRiRBvnA4NnkrMrx\n"
+ "W9UtVjScnw9Xia11FM/IGJIgMpLQ5dqBw930p6FxMYtntRD1AF9sT+YjoCaHv0hX\n"
+ "ZvBEUEF3AgMBAAECggEACCaYpoAbPOX5Dr5y6p47KXboIvrgNFQRPVke62rtOF6M\n"
+ "dQQ3YwKJpCzPxp8qKgbd63KKEfZX2peSHMdKzIGPcSRSRcQ7tlvUN9on1M/rgGIg\n"
+ "3swhI5H0qhdnOLNWdX73qdO6S2pmuiLdTvJ11N4IoLfNj/GnPAr1Ivs1ScL6bkQv\n"
+ "UybaNQ/g2lB0tO7vUeVe2W/AqsIb1eQlf2g+SH7xRj2bGQkr4cWTylqfiVoL/Xic\n"
+ "QVTCks3BWaZhYIhTFgvqVhXZpp52O9J+bxsWJItKQrrCBemxwp82xKbiW/KoI9L1\n"
+ "wSnKvxx7Q3RUN5EvXeOpTRR8QIpBoxP3TTeoj+EOMQKBgQDQb/VfLDlLgfYJpgRC\n"
+ "hKCLW90un9op3nA2n9Dmm9TTLYOmUyiv5ub8QDINEw/YK/NE2JsTSUk2msizqTLL\n"
+ "Z82BFbz9kPlDbJ5MgxG5zXeLvOLurAFmZk/z5JJO+65PKjf0QVLncSAJvMCeNFuC\n"
+ "2yZrEzbrItrjQsN6AedWdx6TTwKBgQDCZAsSI3lQgOh2q1GSxjuIzRAc7JnSGBvD\n"
+ "nG8+SkfKAy7BWe638772Dgx8KYO7TLI4zlm8c9Tr/nkZsGWmM5S2DMI69PWOQWNa\n"
+ "R6QzOFFwNg2JETH7ow+x8+9Q9d3WsPzROz3r5uDXgEk0glthaymVoPILFOiYpz3r\n"
+ "heUbd6mFWQKBgQCCJBVJGhyf54IOHij0u0heGrpr/QTDNY5MnNZa1hs4y2cydyOl\n"
+ "SH8aKp7ViPxQlYhriO6ySQS8YkJD4rXDSImIOmFo1Ja9oVjpHsD3iLFGf2YVbTHm\n"
+ "lKUA+8raI8x+wzZyfELeHMTLL534aWpltp0zJ6kXgQi38pyIVh3x36gogwKBgQCt\n"
+ "nba5k49VVFzLKEXqBjzD+QqMGtFjcH7TnZNJmgQ2K9OFgzIPf5atomyKNHXgQibn\n"
+ "T32cMAQaZqR4SjDvWSBX3FtZVtE+Ja57woKn8IPj6ZL7Oa1fpwpskIbM01s31cln\n"
+ "gjbSy9lC/+PiDw9YmeKBLkcfmKQJO021Xlf6yUxRuQKBgBWPODUO8oKjkuJXNI/w\n"
+ "El9hNWkd+/SZDfnt93dlVyXTtTF0M5M95tlOgqvLtWKSyB/BOnoZYWqR8luMl15d\n"
+ "bf75j5mB0lHMWtyQgvZSkFqe9Or7Zy7hfTShDlZ/w+OXK7PGesaE1F14irShXSji\n"
+ "yn5DZYAZ5pU52xreJeYvDngO\n"
+ "-----END PRIVATE KEY-----\n";
+ const std::string DH =
+ "-----BEGIN DH PARAMETERS-----\n"
+ "MIIBCAKCAQEA4+DA1j0gDWS71okwHpnvA65NmmR4mf+B3H39g163zY5S+cnWS2LI\n"
+ "dvqnUDpw13naWtQ+Nu7I4rk1XoPaxOPSTu1MTbtYOxxU9M1ceBu4kQjDeHwasPVM\n"
+ "zyEs1XXX3tsbPUxAuayX+AgW6QQAQUEjKDnv3FzVnQTFjwI49LqjnrSjbgQcoMaH\n"
+ "EdGGUc6t1/We2vtsJZx0/dbaMkzFYO8dAbEYHL4sPKQb2mLpCPJZC3vwzpFkHFCd\n"
+ "QSnLW2qRhy+66Mf8shdr6uvpoMcnKMOAvjKdXl9PBeJM9eJPz2lC4tnTiM3DqNzK\n"
+ "Hn8+Pu3KkSIFL/5uBVu1fZSq+lFIEI23wwIBAg==\n"
+ "-----END DH PARAMETERS-----\n";
+
+ librbd::ImageCtx *m_image_ctx;
+
+ std::optional<boost::asio::ip::tcp::acceptor> m_acceptor;
+ boost::beast::flat_buffer m_buffer;
+ uint64_t m_server_port = 0;
+};
+
+TEST_F(TestMockMigrationHttpClient, OpenCloseHttp) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ http_client.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, OpenCloseHttps) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTPS));
+ http_client.set_ignore_self_signed_cert(true);
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+
+ boost::asio::ssl::context ssl_context{boost::asio::ssl::context::tlsv12};
+ load_server_certificate(ssl_context);
+ boost::beast::ssl_stream<boost::beast::tcp_stream> ssl_stream{
+ std::move(socket), ssl_context};
+
+ C_SaferCond on_ssl_handshake_ctx;
+ client_ssl_handshake(ssl_stream, false, &on_ssl_handshake_ctx);
+ ASSERT_EQ(0, on_ssl_handshake_ctx.wait());
+
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ http_client.close(&ctx2);
+
+ C_SaferCond on_ssl_shutdown_ctx;
+ client_ssl_shutdown(ssl_stream, &on_ssl_shutdown_ctx);
+ ASSERT_EQ(0, on_ssl_shutdown_ctx.wait());
+
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, OpenHttpsHandshakeFail) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTPS));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+
+ boost::asio::ssl::context ssl_context{boost::asio::ssl::context::tlsv12};
+ load_server_certificate(ssl_context);
+ boost::beast::ssl_stream<boost::beast::tcp_stream> ssl_stream{
+ std::move(socket), ssl_context};
+
+ C_SaferCond on_ssl_handshake_ctx;
+ client_ssl_handshake(ssl_stream, true, &on_ssl_handshake_ctx);
+ ASSERT_NE(0, on_ssl_handshake_ctx.wait());
+ ASSERT_NE(0, ctx1.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, OpenInvalidUrl) {
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx, "ftp://nope/");
+
+ C_SaferCond ctx;
+ http_client.open(&ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, OpenResolveFail) {
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx, "http://foo.example");
+
+ C_SaferCond ctx;
+ http_client.open(&ctx);
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, OpenConnectFail) {
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ "http://localhost:2/");
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(-ECONNREFUSED, ctx1.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, IssueHead) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ EmptyHttpRequest req;
+ req.method(boost::beast::http::verb::head);
+
+ C_SaferCond ctx2;
+ HttpResponse res;
+ http_client.issue(EmptyHttpRequest{req},
+ [&ctx2, &res](int r, HttpResponse&& response) mutable {
+ res = std::move(response);
+ ctx2.complete(r);
+ });
+
+ HttpResponse expected_res;
+ client_read_request(socket, req);
+ client_write_response(socket, expected_res);
+
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(expected_res, res);
+
+ C_SaferCond ctx3;
+ http_client.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, IssueGet) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ EmptyHttpRequest req;
+ req.method(boost::beast::http::verb::get);
+
+ C_SaferCond ctx2;
+ HttpResponse res;
+ http_client.issue(EmptyHttpRequest{req},
+ [&ctx2, &res](int r, HttpResponse&& response) mutable {
+ res = std::move(response);
+ ctx2.complete(r);
+ });
+
+ HttpResponse expected_res;
+ expected_res.body() = "test";
+ client_read_request(socket, req);
+ client_write_response(socket, expected_res);
+
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(expected_res, res);
+
+ C_SaferCond ctx3;
+ http_client.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, IssueSendFailed) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx1;
+ client_accept(&socket, false, &on_connect_ctx1);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx1.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ // close connection to client
+ boost::system::error_code ec;
+ socket.close(ec);
+
+ C_SaferCond on_connect_ctx2;
+ client_accept(&socket, false, &on_connect_ctx2);
+
+ // send request via closed connection
+ EmptyHttpRequest req;
+ req.method(boost::beast::http::verb::get);
+
+ C_SaferCond ctx2;
+ http_client.issue(EmptyHttpRequest{req},
+ [&ctx2](int r, HttpResponse&&) mutable {
+ ctx2.complete(r);
+ });
+
+ // connection will be reset and request retried
+ ASSERT_EQ(0, on_connect_ctx2.wait());
+ HttpResponse expected_res;
+ expected_res.body() = "test";
+ client_read_request(socket, req);
+ client_write_response(socket, expected_res);
+ ASSERT_EQ(0, ctx2.wait());
+
+ C_SaferCond ctx3;
+ http_client.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, IssueReceiveFailed) {
+ boost::asio::ip::tcp::socket socket1(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx1;
+ client_accept(&socket1, false, &on_connect_ctx1);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx1.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ // send request via closed connection
+ EmptyHttpRequest req;
+ req.method(boost::beast::http::verb::get);
+
+ C_SaferCond ctx2;
+ http_client.issue(EmptyHttpRequest{req},
+ [&ctx2](int r, HttpResponse&&) mutable {
+ ctx2.complete(r);
+ });
+
+ // close connection to client after reading request
+ client_read_request(socket1, req);
+
+ C_SaferCond on_connect_ctx2;
+ boost::asio::ip::tcp::socket socket2(*m_image_ctx->asio_engine);
+ client_accept(&socket2, false, &on_connect_ctx2);
+
+ boost::system::error_code ec;
+ socket1.close(ec);
+ ASSERT_EQ(0, on_connect_ctx2.wait());
+
+ // connection will be reset and request retried
+ HttpResponse expected_res;
+ expected_res.body() = "test";
+ client_read_request(socket2, req);
+ client_write_response(socket2, expected_res);
+ ASSERT_EQ(0, ctx2.wait());
+
+ C_SaferCond ctx3;
+ http_client.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, IssueResetFailed) {
+ m_server_port = 0;
+ create_acceptor(true);
+
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx1;
+ client_accept(&socket, false, &on_connect_ctx1);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx1.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ // send requests then close connection
+ EmptyHttpRequest req;
+ req.method(boost::beast::http::verb::get);
+
+ C_SaferCond ctx2;
+ http_client.issue(EmptyHttpRequest{req},
+ [&ctx2](int r, HttpResponse&&) mutable {
+ ctx2.complete(r);
+ });
+
+ C_SaferCond ctx3;
+ http_client.issue(EmptyHttpRequest{req},
+ [&ctx3](int r, HttpResponse&&) mutable {
+ ctx3.complete(r);
+ });
+
+ client_read_request(socket, req);
+ client_read_request(socket, req);
+
+ // close connection to client and verify requests are failed
+ m_acceptor.reset();
+ boost::system::error_code ec;
+ socket.close(ec);
+
+ ASSERT_EQ(-ECONNREFUSED, ctx2.wait());
+ ASSERT_EQ(-ECONNREFUSED, ctx3.wait());
+
+ // additional request will retry the failed connection
+ create_acceptor(true);
+
+ C_SaferCond on_connect_ctx2;
+ client_accept(&socket, false, &on_connect_ctx2);
+
+ C_SaferCond ctx4;
+ http_client.issue(EmptyHttpRequest{req},
+ [&ctx4](int r, HttpResponse&&) mutable {
+ ctx4.complete(r);
+ });
+
+ ASSERT_EQ(0, on_connect_ctx2.wait());
+ client_read_request(socket, req);
+
+ HttpResponse expected_res;
+ expected_res.body() = "test";
+ client_write_response(socket, expected_res);
+ ASSERT_EQ(0, ctx4.wait());
+
+ C_SaferCond ctx5;
+ http_client.close(&ctx5);
+ ASSERT_EQ(0, ctx5.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, IssuePipelined) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ // issue two pipelined (concurrent) get requests
+ EmptyHttpRequest req1;
+ req1.method(boost::beast::http::verb::get);
+
+ C_SaferCond ctx2;
+ HttpResponse res1;
+ http_client.issue(EmptyHttpRequest{req1},
+ [&ctx2, &res1](int r, HttpResponse&& response) mutable {
+ res1 = std::move(response);
+ ctx2.complete(r);
+ });
+
+ EmptyHttpRequest req2;
+ req2.method(boost::beast::http::verb::get);
+
+ C_SaferCond ctx3;
+ HttpResponse res2;
+ http_client.issue(EmptyHttpRequest{req2},
+ [&ctx3, &res2](int r, HttpResponse&& response) mutable {
+ res2 = std::move(response);
+ ctx3.complete(r);
+ });
+
+ client_read_request(socket, req1);
+ client_read_request(socket, req2);
+
+ // read the responses sequentially
+ HttpResponse expected_res1;
+ expected_res1.body() = "test";
+ client_write_response(socket, expected_res1);
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(expected_res1, res1);
+
+ HttpResponse expected_res2;
+ expected_res2.body() = "test";
+ client_write_response(socket, expected_res2);
+ ASSERT_EQ(0, ctx3.wait());
+ ASSERT_EQ(expected_res2, res2);
+
+ C_SaferCond ctx4;
+ http_client.close(&ctx4);
+ ASSERT_EQ(0, ctx4.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, IssuePipelinedRestart) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx1;
+ client_accept(&socket, false, &on_connect_ctx1);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx1.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ // issue two pipelined (concurrent) get requests
+ EmptyHttpRequest req1;
+ req1.keep_alive(false);
+ req1.method(boost::beast::http::verb::get);
+
+ C_SaferCond on_connect_ctx2;
+ client_accept(&socket, false, &on_connect_ctx2);
+
+ C_SaferCond ctx2;
+ HttpResponse res1;
+ http_client.issue(EmptyHttpRequest{req1},
+ [&ctx2, &res1](int r, HttpResponse&& response) mutable {
+ res1 = std::move(response);
+ ctx2.complete(r);
+ });
+
+ EmptyHttpRequest req2;
+ req2.method(boost::beast::http::verb::get);
+
+ C_SaferCond ctx3;
+ HttpResponse res2;
+ http_client.issue(EmptyHttpRequest{req2},
+ [&ctx3, &res2](int r, HttpResponse&& response) mutable {
+ res2 = std::move(response);
+ ctx3.complete(r);
+ });
+
+ client_read_request(socket, req1);
+ client_read_request(socket, req2);
+
+ // read the responses sequentially
+ HttpResponse expected_res1;
+ expected_res1.body() = "test";
+ expected_res1.keep_alive(false);
+ client_write_response(socket, expected_res1);
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(expected_res1, res1);
+
+ // second request will need to be re-sent due to 'need_eof' condition
+ ASSERT_EQ(0, on_connect_ctx2.wait());
+ client_read_request(socket, req2);
+
+ HttpResponse expected_res2;
+ expected_res2.body() = "test";
+ client_write_response(socket, expected_res2);
+ ASSERT_EQ(0, ctx3.wait());
+ ASSERT_EQ(expected_res2, res2);
+
+ C_SaferCond ctx4;
+ http_client.close(&ctx4);
+ ASSERT_EQ(0, ctx4.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, ShutdownInFlight) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ EmptyHttpRequest req;
+ req.method(boost::beast::http::verb::get);
+
+ C_SaferCond ctx2;
+ http_client.issue(EmptyHttpRequest{req},
+ [&ctx2](int r, HttpResponse&&) mutable {
+ ctx2.complete(r);
+ });
+
+ client_read_request(socket, req);
+
+ C_SaferCond ctx3;
+ http_client.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+ ASSERT_EQ(-ESHUTDOWN, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, GetSize) {
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ uint64_t size = 0;
+ C_SaferCond ctx2;
+ http_client.get_size(&size, &ctx2);
+
+ EmptyHttpRequest expected_req;
+ expected_req.method(boost::beast::http::verb::head);
+ client_read_request(socket, expected_req);
+
+ HttpResponse expected_res;
+ expected_res.body() = std::string(123, '1');
+ client_write_response(socket, expected_res);
+
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(123, size);
+
+ C_SaferCond ctx3;
+ http_client.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, GetSizeError) {
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ uint64_t size = 0;
+ C_SaferCond ctx2;
+ http_client.get_size(&size, &ctx2);
+
+ EmptyHttpRequest expected_req;
+ expected_req.method(boost::beast::http::verb::head);
+ client_read_request(socket, expected_req);
+
+ HttpResponse expected_res;
+ expected_res.result(boost::beast::http::status::internal_server_error);
+ client_write_response(socket, expected_res);
+
+ ASSERT_EQ(-EIO, ctx2.wait());
+
+ C_SaferCond ctx3;
+ http_client.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, Read) {
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ bufferlist bl;
+ C_SaferCond ctx2;
+ http_client.read({{0, 128}, {256, 64}}, &bl, &ctx2);
+
+ EmptyHttpRequest expected_req1;
+ expected_req1.method(boost::beast::http::verb::get);
+ expected_req1.set(boost::beast::http::field::range, "bytes=0-127");
+ client_read_request(socket, expected_req1);
+
+ EmptyHttpRequest expected_req2;
+ expected_req2.method(boost::beast::http::verb::get);
+ expected_req2.set(boost::beast::http::field::range, "bytes=256-319");
+ client_read_request(socket, expected_req2);
+
+ HttpResponse expected_res1;
+ expected_res1.result(boost::beast::http::status::partial_content);
+ expected_res1.body() = std::string(128, '1');
+ client_write_response(socket, expected_res1);
+
+ HttpResponse expected_res2;
+ expected_res2.result(boost::beast::http::status::partial_content);
+ expected_res2.body() = std::string(64, '2');
+ client_write_response(socket, expected_res2);
+
+ ASSERT_EQ(192, ctx2.wait());
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(128, '1'));
+ expect_bl.append(std::string(64, '2'));
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ http_client.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+} // namespace migration
+} // namespace librbd
diff --git a/src/test/librbd/migration/test_mock_HttpStream.cc b/src/test/librbd/migration/test_mock_HttpStream.cc
new file mode 100644
index 000000000..aff22b757
--- /dev/null
+++ b/src/test/librbd/migration/test_mock_HttpStream.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 "include/rbd_types.h"
+#include "common/ceph_mutex.h"
+#include "librbd/migration/HttpClient.h"
+#include "librbd/migration/HttpStream.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "json_spirit/json_spirit.h"
+#include <boost/beast/http.hpp>
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace migration {
+
+template <>
+struct HttpClient<MockTestImageCtx> {
+ static HttpClient* s_instance;
+ static HttpClient* create(MockTestImageCtx*, const std::string&) {
+ ceph_assert(s_instance != nullptr);
+ return s_instance;
+ }
+
+ MOCK_METHOD1(open, void(Context*));
+ MOCK_METHOD1(close, void(Context*));
+ MOCK_METHOD2(get_size, void(uint64_t*, Context*));
+ MOCK_METHOD3(do_read, void(const io::Extents&, bufferlist*, Context*));
+ void read(io::Extents&& extents, bufferlist* bl, Context* ctx) {
+ do_read(extents, bl, ctx);
+ }
+
+ HttpClient() {
+ s_instance = this;
+ }
+};
+
+HttpClient<MockTestImageCtx>* HttpClient<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace migration
+} // namespace librbd
+
+#include "librbd/migration/HttpStream.cc"
+
+namespace librbd {
+namespace migration {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InSequence;
+using ::testing::WithArgs;
+
+class TestMockMigrationHttpStream : public TestMockFixture {
+public:
+ typedef HttpStream<MockTestImageCtx> MockHttpStream;
+ typedef HttpClient<MockTestImageCtx> MockHttpClient;
+
+ librbd::ImageCtx *m_image_ctx;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+ json_object["url"] = "http://some.site/file";
+ }
+
+ void expect_open(MockHttpClient& mock_http_client, int r) {
+ EXPECT_CALL(mock_http_client, open(_))
+ .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); }));
+ }
+
+ void expect_close(MockHttpClient& mock_http_client, int r) {
+ EXPECT_CALL(mock_http_client, close(_))
+ .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); }));
+ }
+
+ void expect_get_size(MockHttpClient& mock_http_client, uint64_t size, int r) {
+ EXPECT_CALL(mock_http_client, get_size(_, _))
+ .WillOnce(Invoke([size, r](uint64_t* out_size, Context* ctx) {
+ *out_size = size;
+ ctx->complete(r);
+ }));
+ }
+
+ void expect_read(MockHttpClient& mock_http_client, io::Extents byte_extents,
+ const bufferlist& bl, int r) {
+ uint64_t len = 0;
+ for (auto [_, byte_len] : byte_extents) {
+ len += byte_len;
+ }
+ EXPECT_CALL(mock_http_client, do_read(byte_extents, _, _))
+ .WillOnce(WithArgs<1, 2>(Invoke(
+ [len, bl, r](bufferlist* out_bl, Context* ctx) {
+ *out_bl = bl;
+ ctx->complete(r < 0 ? r : len);
+ })));
+ }
+
+ json_spirit::mObject json_object;
+};
+
+TEST_F(TestMockMigrationHttpStream, OpenClose) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ auto mock_http_client = new MockHttpClient();
+ expect_open(*mock_http_client, 0);
+
+ expect_close(*mock_http_client, 0);
+
+ MockHttpStream mock_http_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_http_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_http_stream.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationHttpStream, GetSize) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ auto mock_http_client = new MockHttpClient();
+ expect_open(*mock_http_client, 0);
+
+ expect_get_size(*mock_http_client, 128, 0);
+
+ expect_close(*mock_http_client, 0);
+
+ MockHttpStream mock_http_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_http_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ uint64_t size;
+ mock_http_stream.get_size(&size, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(128, size);
+
+ C_SaferCond ctx3;
+ mock_http_stream.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationHttpStream, Read) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ auto mock_http_client = new MockHttpClient();
+ expect_open(*mock_http_client, 0);
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(192, '1'));
+ expect_read(*mock_http_client, {{0, 128}, {256, 64}}, expect_bl, 0);
+
+ expect_close(*mock_http_client, 0);
+
+ MockHttpStream mock_http_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_http_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ bufferlist bl;
+ mock_http_stream.read({{0, 128}, {256, 64}}, &bl, &ctx2);
+ ASSERT_EQ(192, ctx2.wait());
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ mock_http_stream.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+} // namespace migration
+} // namespace librbd
diff --git a/src/test/librbd/migration/test_mock_QCOWFormat.cc b/src/test/librbd/migration/test_mock_QCOWFormat.cc
new file mode 100644
index 000000000..6e7225d22
--- /dev/null
+++ b/src/test/librbd/migration/test_mock_QCOWFormat.cc
@@ -0,0 +1,1259 @@
+// -*- 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/migration/MockStreamInterface.h"
+#include "include/rbd_types.h"
+#include "common/ceph_mutex.h"
+#include "librbd/migration/QCOWFormat.h"
+#include "librbd/migration/SourceSpecBuilder.h"
+#include "acconfig.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "json_spirit/json_spirit.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace migration {
+
+template<>
+struct SourceSpecBuilder<librbd::MockTestImageCtx> {
+
+ MOCK_CONST_METHOD2(build_stream, int(const json_spirit::mObject&,
+ std::shared_ptr<StreamInterface>*));
+
+};
+
+} // namespace migration
+
+bool operator==(const SnapInfo& lhs, const SnapInfo& rhs) {
+ return (lhs.name == rhs.name &&
+ lhs.size == rhs.size);
+}
+
+} // namespace librbd
+
+#include "librbd/migration/QCOWFormat.cc"
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::ReturnRef;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+
+namespace librbd {
+namespace migration {
+
+using ::testing::Invoke;
+
+class TestMockMigrationQCOWFormat : public TestMockFixture {
+public:
+ typedef QCOWFormat<MockTestImageCtx> MockQCOWFormat;
+ typedef SourceSpecBuilder<MockTestImageCtx> MockSourceSpecBuilder;
+
+ librbd::ImageCtx *m_image_ctx;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+
+ json_spirit::mObject stream_obj;
+ stream_obj["type"] = "file";
+ json_object["stream"] = stream_obj;
+ }
+
+ void expect_build_stream(MockSourceSpecBuilder& mock_source_spec_builder,
+ MockStreamInterface* mock_stream_interface, int r) {
+ EXPECT_CALL(mock_source_spec_builder, build_stream(_, _))
+ .WillOnce(WithArgs<1>(Invoke([mock_stream_interface, r]
+ (std::shared_ptr<StreamInterface>* ptr) {
+ ptr->reset(mock_stream_interface);
+ return r;
+ })));
+ }
+
+ void expect_stream_open(MockStreamInterface& mock_stream_interface, int r) {
+ EXPECT_CALL(mock_stream_interface, open(_))
+ .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); }));
+ }
+
+ void expect_stream_close(MockStreamInterface& mock_stream_interface, int r) {
+ EXPECT_CALL(mock_stream_interface, close(_))
+ .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); }));
+ }
+
+ void expect_stream_read(MockStreamInterface& mock_stream_interface,
+ const io::Extents& byte_extents,
+ const bufferlist& bl, int r) {
+ EXPECT_CALL(mock_stream_interface, read(byte_extents, _, _))
+ .WillOnce(WithArgs<1, 2>(Invoke([bl, r]
+ (bufferlist* out_bl, Context* ctx) {
+ *out_bl = bl;
+ ctx->complete(r);
+ })));
+ }
+
+ void expect_probe_header(MockStreamInterface& mock_stream_interface,
+ uint32_t magic, uint32_t version, int r) {
+ magic = htobe32(magic);
+ version = htobe32(version);
+
+ bufferlist probe_bl;
+ probe_bl.append(std::string_view(reinterpret_cast<char*>(&magic), 4));
+ probe_bl.append(std::string_view(reinterpret_cast<char*>(&version), 4));
+ expect_stream_read(mock_stream_interface, {{0, 8}}, probe_bl, r);
+ }
+
+ void expect_read_header(MockStreamInterface& mock_stream_interface,
+ uint32_t snapshot_count, int r) {
+ QCowHeader qcow_header;
+ memset(&qcow_header, 0, sizeof(qcow_header));
+ qcow_header.magic = htobe32(QCOW_MAGIC);
+ qcow_header.version = htobe32(2);
+ qcow_header.size = htobe64(1<<30);
+ qcow_header.cluster_bits = htobe32(16);
+ qcow_header.l1_size = htobe32(2);
+ qcow_header.l1_table_offset = htobe64(1<<20);
+ if (snapshot_count > 0) {
+ qcow_header.nb_snapshots = htobe32(snapshot_count);
+ qcow_header.snapshots_offset = htobe64(1<<21);
+ }
+
+ bufferlist header_bl;
+ header_bl.append(std::string_view(reinterpret_cast<char*>(&qcow_header),
+ sizeof(qcow_header)));
+ expect_stream_read(mock_stream_interface, {{0, sizeof(qcow_header)}},
+ header_bl, r);
+ }
+
+ void expect_read_l1_table(MockStreamInterface& mock_stream_interface,
+ std::optional<std::vector<uint64_t>>&& l1_table,
+ int r) {
+ bufferlist l1_table_bl;
+ if (!l1_table) {
+ l1_table.emplace(2);
+ }
+
+ l1_table->resize(2);
+ for (size_t idx = 0; idx < l1_table->size(); ++idx) {
+ (*l1_table)[idx] = htobe64((*l1_table)[idx]);
+ }
+
+ l1_table_bl.append(
+ std::string_view(reinterpret_cast<char*>(l1_table->data()), 16));
+ expect_stream_read(mock_stream_interface, {{1<<20, 16}}, l1_table_bl, r);
+ }
+
+ void expect_read_l2_table(MockStreamInterface& mock_stream_interface,
+ uint64_t l2_table_offset,
+ std::optional<std::vector<uint64_t>>&& l2_table,
+ int r) {
+ size_t l2_table_size = 1<<(16 - 3); // cluster_bit - 3 bits for offset
+ bufferlist l2_table_bl;
+ if (!l2_table) {
+ l2_table.emplace(l2_table_size);
+ }
+
+ l2_table->resize(l2_table_size);
+ for (size_t idx = 0; idx < l2_table->size(); ++idx) {
+ (*l2_table)[idx] = htobe64((*l2_table)[idx]);
+ }
+
+ l2_table_bl.append(
+ std::string_view(reinterpret_cast<char*>(l2_table->data()),
+ l2_table->size() * sizeof(uint64_t)));
+ expect_stream_read(mock_stream_interface,
+ {{l2_table_offset, l2_table->size() * sizeof(uint64_t)}},
+ l2_table_bl, r);
+ }
+
+ void expect_read_cluster(MockStreamInterface& mock_stream_interface,
+ uint64_t cluster_offset, uint32_t offset,
+ const bufferlist& bl, int r) {
+ uint32_t cluster_size = 1<<16;
+
+ bufferlist cluster_bl;
+ if (offset > 0) {
+ cluster_size -= offset;
+ cluster_bl.append_zero(offset);
+ }
+
+ cluster_size -= bl.length();
+ cluster_bl.append(bl);
+
+ if (cluster_size > 0) {
+ cluster_bl.append_zero(cluster_size);
+ }
+
+ expect_stream_read(mock_stream_interface,
+ {{cluster_offset, cluster_bl.length()}}, cluster_bl, r);
+ }
+
+ void expect_open(MockStreamInterface& mock_stream_interface,
+ std::optional<std::vector<uint64_t>>&& l1_table) {
+ expect_stream_open(mock_stream_interface, 0);
+
+ expect_probe_header(mock_stream_interface, QCOW_MAGIC, 2, 0);
+
+ expect_read_header(mock_stream_interface, 0, 0);
+
+ expect_read_l1_table(mock_stream_interface, std::move(l1_table), 0);
+ }
+
+ void expect_read_snapshot_header(MockStreamInterface& mock_stream_interface,
+ const std::string& id,
+ const std::string& name,
+ uint64_t l1_table_offset,
+ uint64_t* snapshot_offset, int r) {
+ QCowSnapshotHeader snapshot_header;
+ memset(&snapshot_header, 0, sizeof(snapshot_header));
+ snapshot_header.id_str_size = htobe16(id.size());
+ snapshot_header.name_size = htobe16(name.size());
+ snapshot_header.extra_data_size = htobe32(16);
+ snapshot_header.l1_size = htobe32(1);
+ snapshot_header.l1_table_offset = htobe64(l1_table_offset);
+
+ bufferlist snapshot_header_bl;
+ snapshot_header_bl.append(
+ std::string_view(reinterpret_cast<char*>(&snapshot_header),
+ sizeof(snapshot_header)));
+ expect_stream_read(mock_stream_interface,
+ {{*snapshot_offset, sizeof(snapshot_header)}},
+ snapshot_header_bl, r);
+ *snapshot_offset += sizeof(snapshot_header);
+ }
+
+ void expect_read_snapshot_header_extra(
+ MockStreamInterface& mock_stream_interface,
+ const std::string& id, const std::string& name, uint64_t size,
+ uint64_t* snapshot_offset, int r) {
+ QCowSnapshotExtraData snapshot_header_extra;
+ memset(&snapshot_header_extra, 0, sizeof(snapshot_header_extra));
+ snapshot_header_extra.disk_size = htobe64(size);
+
+ bufferlist snapshot_header_extra_bl;
+ snapshot_header_extra_bl.append(
+ std::string_view(reinterpret_cast<char*>(&snapshot_header_extra), 16));
+ snapshot_header_extra_bl.append(id);
+ snapshot_header_extra_bl.append(name);
+ expect_stream_read(mock_stream_interface,
+ {{*snapshot_offset, 16 + id.size() + name.size()}},
+ snapshot_header_extra_bl, r);
+
+ *snapshot_offset += 16 + id.size() + name.size();
+ *snapshot_offset = p2roundup(*snapshot_offset, static_cast<uint64_t>(8));
+ }
+
+ void expect_read_snapshot_l1_table(MockStreamInterface& mock_stream_interface,
+ uint64_t l1_table_offset, int r) {
+ uint64_t l2_table_cluster = htobe64(l1_table_offset);
+
+ bufferlist snapshot_l1_table_bl;
+ snapshot_l1_table_bl.append(
+ std::string_view(reinterpret_cast<char*>(&l2_table_cluster), 8));
+ expect_stream_read(mock_stream_interface, {{l1_table_offset, 8}},
+ snapshot_l1_table_bl, r);
+ }
+
+
+ void expect_read_snapshot(MockStreamInterface& mock_stream_interface,
+ const std::string& id, const std::string& name,
+ uint64_t size, uint64_t l1_table_offset,
+ uint64_t* snapshot_offset) {
+ expect_read_snapshot_header(mock_stream_interface, id, name,
+ l1_table_offset, snapshot_offset, 0);
+ expect_read_snapshot_header_extra(mock_stream_interface, id, name,
+ size, snapshot_offset, 0);
+ expect_read_snapshot_l1_table(mock_stream_interface, l1_table_offset, 0);
+ }
+
+ json_spirit::mObject json_object;
+};
+
+TEST_F(TestMockMigrationQCOWFormat, OpenCloseV1) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ int expected_open_ret_val = 0;
+ QCowHeaderV1 qcow_header;
+ memset(&qcow_header, 0, sizeof(qcow_header));
+ qcow_header.magic = htobe32(QCOW_MAGIC);
+ qcow_header.version = htobe32(1);
+ qcow_header.size = htobe64(1<<30);
+ qcow_header.l1_table_offset = htobe64(1<<20);
+ qcow_header.cluster_bits = 16;
+ qcow_header.l2_bits = 13;
+
+ bufferlist probe_bl;
+ probe_bl.append(std::string_view(reinterpret_cast<char*>(&qcow_header), 8));
+ expect_stream_read(*mock_stream_interface, {{0, 8}}, probe_bl, 0);
+
+#ifdef WITH_RBD_MIGRATION_FORMAT_QCOW_V1
+
+ bufferlist header_bl;
+ header_bl.append(std::string_view(reinterpret_cast<char*>(&qcow_header),
+ sizeof(qcow_header)));
+ expect_stream_read(*mock_stream_interface, {{0, sizeof(qcow_header)}},
+ header_bl, 0);
+
+ bufferlist l1_table_bl;
+ l1_table_bl.append_zero(16);
+ expect_stream_read(*mock_stream_interface, {{1<<20, 16}}, l1_table_bl, 0);
+
+#else // WITH_RBD_MIGRATION_FORMAT_QCOW_V1
+
+ expected_open_ret_val = -ENOTSUP;
+
+#endif // WITH_RBD_MIGRATION_FORMAT_QCOW_V1
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(expected_open_ret_val, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_qcow_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, OpenCloseV2) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_open(*mock_stream_interface, {});
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_qcow_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ProbeInvalidMagic) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, 0, 2, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(-EINVAL, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_qcow_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ProbeInvalidVersion) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 0, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(-EINVAL, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_qcow_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ProbeError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, -EIO);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(-EIO, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_qcow_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+#ifdef WITH_RBD_MIGRATION_FORMAT_QCOW_V1
+
+TEST_F(TestMockMigrationQCOWFormat, ReadHeaderV1Error) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 1, 0);
+
+ QCowHeaderV1 qcow_header;
+ memset(&qcow_header, 0, sizeof(qcow_header));
+ qcow_header.magic = htobe32(QCOW_MAGIC);
+ qcow_header.version = htobe32(1);
+ qcow_header.size = htobe64(1<<30);
+ qcow_header.l1_table_offset = htobe64(1<<20);
+ qcow_header.cluster_bits = 16;
+ qcow_header.l2_bits = 13;
+
+ bufferlist header_bl;
+ header_bl.append(std::string_view(reinterpret_cast<char*>(&qcow_header),
+ sizeof(qcow_header)));
+ expect_stream_read(*mock_stream_interface, {{0, sizeof(qcow_header)}},
+ header_bl, -EPERM);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(-EPERM, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_qcow_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+#endif // WITH_RBD_MIGRATION_FORMAT_QCOW_V1
+
+TEST_F(TestMockMigrationQCOWFormat, ReadHeaderV2Error) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0);
+
+ expect_read_header(*mock_stream_interface, 0, -EIO);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(-EIO, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_qcow_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ReadL1TableError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0);
+
+ expect_read_header(*mock_stream_interface, 0, 0);
+
+ expect_read_l1_table(*mock_stream_interface, {}, -EPERM);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(-EPERM, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_qcow_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, Snapshots) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0);
+
+ expect_read_header(*mock_stream_interface, 2, 0);
+
+ uint64_t snapshot_offset = 1<<21;
+ expect_read_snapshot(*mock_stream_interface, "1", "snap1", 1<<29, 1<<22,
+ &snapshot_offset);
+ expect_read_snapshot(*mock_stream_interface, "2", "snap2", 1<<29, 1<<22,
+ &snapshot_offset);
+
+ expect_read_l1_table(*mock_stream_interface, {}, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ FormatInterface::SnapInfos snap_infos;
+ C_SaferCond ctx2;
+ mock_qcow_format.get_snapshots(&snap_infos, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+
+ FormatInterface::SnapInfos expected_snap_infos{
+ {1, {"snap1", cls::rbd::UserSnapshotNamespace{}, 1<<29, {}, 0, 0, {}}},
+ {2, {"snap2", cls::rbd::UserSnapshotNamespace{}, 1<<29, {}, 0, 0, {}}}};
+ ASSERT_EQ(expected_snap_infos, snap_infos);
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, SnapshotHeaderError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0);
+
+ expect_read_header(*mock_stream_interface, 2, 0);
+
+ uint64_t snapshot_offset = 1<<21;
+ expect_read_snapshot_header(*mock_stream_interface, "1", "snap1",
+ 1<<22, &snapshot_offset, -EINVAL);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(-EINVAL, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_qcow_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, SnapshotExtraError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0);
+
+ expect_read_header(*mock_stream_interface, 2, 0);
+
+ uint64_t snapshot_offset = 1<<21;
+ expect_read_snapshot_header(*mock_stream_interface, "1", "snap1",
+ 1<<22, &snapshot_offset, 0);
+ expect_read_snapshot_header_extra(*mock_stream_interface, "1", "snap1",
+ 1<<29, &snapshot_offset, -EINVAL);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(-EINVAL, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_qcow_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, SnapshotL1TableError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0);
+
+ expect_read_header(*mock_stream_interface, 2, 0);
+
+ uint64_t snapshot_offset = 1<<21;
+ expect_read_snapshot_header(*mock_stream_interface, "1", "snap1",
+ 1<<22, &snapshot_offset, 0);
+ expect_read_snapshot_header_extra(*mock_stream_interface, "1", "snap1",
+ 1<<29, &snapshot_offset, 0);
+ expect_read_snapshot_l1_table(*mock_stream_interface, 1<<22, -EINVAL);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(-EINVAL, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_qcow_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, GetImageSize) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_open(*mock_stream_interface, {});
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ uint64_t size = 0;
+ C_SaferCond ctx2;
+ mock_qcow_format.get_image_size(CEPH_NOSNAP, &size, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(1<<30, size);
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, GetImageSizeSnap) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0);
+
+ expect_read_header(*mock_stream_interface, 1, 0);
+
+ uint64_t snapshot_offset = 1<<21;
+ expect_read_snapshot(*mock_stream_interface, "1", "snap1", 1<<29, 1<<22,
+ &snapshot_offset);
+
+ expect_read_l1_table(*mock_stream_interface, {}, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ uint64_t size = 0;
+ C_SaferCond ctx2;
+ mock_qcow_format.get_image_size(1U, &size, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(1<<29, size);
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, GetImageSizeSnapDNE) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_open(*mock_stream_interface, {});
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ uint64_t size = 0;
+ C_SaferCond ctx2;
+ mock_qcow_format.get_image_size(1U, &size, &ctx2);
+ ASSERT_EQ(-ENOENT, ctx2.wait());
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, Read) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_open(*mock_stream_interface, {{1ULL<<40}});
+
+ expect_read_l2_table(*mock_stream_interface, 1ULL<<40,
+ {{0, 1ULL<<32}}, 0);
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(123, '1'));
+ expect_read_cluster(*mock_stream_interface, 1ULL<<32, 123, expect_bl, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ auto aio_comp = io::AioCompletion::create_and_start(
+ &ctx2, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl;
+ io::ReadResult read_result{&bl};
+ ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{65659, 123}},
+ std::move(read_result), 0, 0, {}));
+ ASSERT_EQ(123, ctx2.wait());
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ReadL1DNE) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_open(*mock_stream_interface, {});
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ auto aio_comp = io::AioCompletion::create_and_start(
+ &ctx2, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl;
+ io::ReadResult read_result{&bl};
+ ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{234, 123}},
+ std::move(read_result), 0, 0, {}));
+ ASSERT_EQ(123, ctx2.wait());
+ bufferlist expect_bl;
+ expect_bl.append_zero(123);
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ReadL2DNE) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_open(*mock_stream_interface, {{1ULL<<40}});
+
+ expect_read_l2_table(*mock_stream_interface, 1ULL<<40,
+ {{0, 1ULL<<32}}, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ auto aio_comp = io::AioCompletion::create_and_start(
+ &ctx2, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl;
+ io::ReadResult read_result{&bl};
+ ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{234, 123}},
+ std::move(read_result), 0, 0, {}));
+ ASSERT_EQ(123, ctx2.wait());
+ bufferlist expect_bl;
+ expect_bl.append_zero(123);
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ReadZero) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_open(*mock_stream_interface, {{1ULL<<40}});
+
+ expect_read_l2_table(*mock_stream_interface, 1ULL<<40,
+ {{0, QCOW_OFLAG_ZERO}}, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ auto aio_comp = io::AioCompletion::create_and_start(
+ &ctx2, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl;
+ io::ReadResult read_result{&bl};
+ ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{65659, 123}},
+ std::move(read_result), 0, 0, {}));
+ ASSERT_EQ(123, ctx2.wait());
+ bufferlist expect_bl;
+ expect_bl.append_zero(123);
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ReadSnap) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0);
+
+ expect_read_header(*mock_stream_interface, 1, 0);
+
+ uint64_t snapshot_offset = 1<<21;
+ expect_read_snapshot(*mock_stream_interface, "1", "snap1", 1<<29, 1<<22,
+ &snapshot_offset);
+
+ expect_read_l1_table(*mock_stream_interface, {}, 0);
+
+ expect_read_l2_table(*mock_stream_interface, 1<<22,
+ {{0, 1ULL<<32}}, 0);
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(123, '1'));
+ expect_read_cluster(*mock_stream_interface, 1ULL<<32, 123, expect_bl, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ auto aio_comp = io::AioCompletion::create_and_start(
+ &ctx2, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl;
+ io::ReadResult read_result{&bl};
+ ASSERT_TRUE(mock_qcow_format.read(aio_comp, 1, {{65659, 123}},
+ std::move(read_result), 0, 0, {}));
+ ASSERT_EQ(123, ctx2.wait());
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ReadSnapDNE) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_open(*mock_stream_interface, {{1ULL<<40}});
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ auto aio_comp = io::AioCompletion::create_and_start(
+ &ctx2, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl;
+ io::ReadResult read_result{&bl};
+ ASSERT_TRUE(mock_qcow_format.read(aio_comp, 1, {{65659, 123}},
+ std::move(read_result), 0, 0, {}));
+ ASSERT_EQ(-ENOENT, ctx2.wait());
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ReadClusterCacheHit) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_open(*mock_stream_interface, {{1ULL<<40}});
+
+ expect_read_l2_table(*mock_stream_interface, 1ULL<<40,
+ {{0, 1ULL<<32}}, 0);
+
+ bufferlist expect_bl1;
+ expect_bl1.append(std::string(123, '1'));
+
+ bufferlist expect_bl2;
+ expect_bl2.append(std::string(234, '2'));
+
+ bufferlist cluster_bl;
+ cluster_bl.append_zero(123);
+ cluster_bl.append(expect_bl1);
+ cluster_bl.append_zero(234);
+ cluster_bl.append(expect_bl2);
+
+ expect_read_cluster(*mock_stream_interface, 1ULL<<32, 0, cluster_bl, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ auto aio_comp1 = io::AioCompletion::create_and_start(
+ &ctx2, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl1;
+ io::ReadResult read_result1{&bl1};
+ ASSERT_TRUE(mock_qcow_format.read(aio_comp1, CEPH_NOSNAP, {{65659, 123}},
+ std::move(read_result1), 0, 0, {}));
+ ASSERT_EQ(123, ctx2.wait());
+ ASSERT_EQ(expect_bl1, bl1);
+
+ C_SaferCond ctx3;
+ auto aio_comp2 = io::AioCompletion::create_and_start(
+ &ctx3, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl2;
+ io::ReadResult read_result2{&bl2};
+ ASSERT_TRUE(mock_qcow_format.read(aio_comp2, CEPH_NOSNAP, {{66016, 234}},
+ std::move(read_result2), 0, 0, {}));
+ ASSERT_EQ(234, ctx3.wait());
+ ASSERT_EQ(expect_bl2, bl2);
+
+ C_SaferCond ctx4;
+ mock_qcow_format.close(&ctx4);
+ ASSERT_EQ(0, ctx4.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ReadClusterError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_open(*mock_stream_interface, {{1ULL<<40}});
+
+ expect_read_l2_table(*mock_stream_interface, 1ULL<<40,
+ {{0, 1ULL<<32}}, 0);
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(123, '1'));
+ expect_read_cluster(*mock_stream_interface, 1ULL<<32, 123, expect_bl, -EIO);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ auto aio_comp = io::AioCompletion::create_and_start(
+ &ctx2, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl;
+ io::ReadResult read_result{&bl};
+ ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{65659, 123}},
+ std::move(read_result), 0, 0, {}));
+ ASSERT_EQ(-EIO, ctx2.wait());
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ReadL2TableError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_open(*mock_stream_interface, {{1ULL<<40}});
+
+ expect_read_l2_table(*mock_stream_interface, 1ULL<<40,
+ {{0, 1ULL<<32}}, -EIO);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ auto aio_comp = io::AioCompletion::create_and_start(
+ &ctx2, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl;
+ io::ReadResult read_result{&bl};
+ ASSERT_TRUE(mock_qcow_format.read(aio_comp, CEPH_NOSNAP, {{65659, 123}},
+ std::move(read_result), 0, 0, {}));
+ ASSERT_EQ(-EIO, ctx2.wait());
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationQCOWFormat, ListSnaps) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+
+ expect_probe_header(*mock_stream_interface, QCOW_MAGIC, 2, 0);
+
+ expect_read_header(*mock_stream_interface, 2, 0);
+
+ uint64_t snapshot_offset = 1<<21;
+ expect_read_snapshot(*mock_stream_interface, "1", "snap1", 1<<29, 1<<22,
+ &snapshot_offset);
+ expect_read_snapshot(*mock_stream_interface, "2", "snap2", 1<<29, 1<<23,
+ &snapshot_offset);
+
+ expect_read_l1_table(*mock_stream_interface, {{1ULL<<28}}, 0);
+
+ io::SparseExtents sparse_extents_1;
+ sparse_extents_1.insert(0, 196608, {io::SPARSE_EXTENT_STATE_DATA, 196608});
+ expect_read_l2_table(*mock_stream_interface, 1ULL<<22,
+ {{1ULL<<23, 1ULL<<24, 1ULL<<25}}, 0);
+
+ io::SparseExtents sparse_extents_2;
+ sparse_extents_2.insert(0, 65536, {io::SPARSE_EXTENT_STATE_DATA, 65536});
+ sparse_extents_2.insert(65536, 65536,
+ {io::SPARSE_EXTENT_STATE_ZEROED, 65536});
+ expect_read_l2_table(*mock_stream_interface, 1ULL<<23,
+ {{1ULL<<26, QCOW_OFLAG_ZERO, 1ULL<<25}}, 0);
+
+ io::SparseExtents sparse_extents_head;
+ sparse_extents_head.insert(131072, 65536,
+ {io::SPARSE_EXTENT_STATE_DATA, 65536});
+ expect_read_l2_table(*mock_stream_interface, 1ULL<<28,
+ {{1ULL<<26, QCOW_OFLAG_ZERO, 1ULL<<27}}, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockQCOWFormat mock_qcow_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_qcow_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ io::SnapshotDelta snapshot_delta;
+ mock_qcow_format.list_snaps({{0, 196608}}, {1, CEPH_NOSNAP}, 0,
+ &snapshot_delta, {}, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+
+ io::SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{1, 1}] = sparse_extents_1;
+ expected_snapshot_delta[{CEPH_NOSNAP, 2}] = sparse_extents_2;
+ expected_snapshot_delta[{CEPH_NOSNAP, CEPH_NOSNAP}] = sparse_extents_head;
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+
+ C_SaferCond ctx3;
+ mock_qcow_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+} // namespace migration
+} // namespace librbd
diff --git a/src/test/librbd/migration/test_mock_RawFormat.cc b/src/test/librbd/migration/test_mock_RawFormat.cc
new file mode 100644
index 000000000..6b69bf20c
--- /dev/null
+++ b/src/test/librbd/migration/test_mock_RawFormat.cc
@@ -0,0 +1,523 @@
+// -*- 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/migration/MockSnapshotInterface.h"
+#include "include/rbd_types.h"
+#include "common/ceph_mutex.h"
+#include "librbd/migration/RawFormat.h"
+#include "librbd/migration/SourceSpecBuilder.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "json_spirit/json_spirit.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace migration {
+
+template<>
+struct SourceSpecBuilder<librbd::MockTestImageCtx> {
+
+ MOCK_CONST_METHOD3(build_snapshot, int(const json_spirit::mObject&, uint64_t,
+ std::shared_ptr<SnapshotInterface>*));
+
+};
+
+} // namespace migration
+} // namespace librbd
+
+#include "librbd/migration/RawFormat.cc"
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::ReturnRef;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+
+namespace librbd {
+namespace migration {
+
+using ::testing::Invoke;
+
+class TestMockMigrationRawFormat : public TestMockFixture {
+public:
+ typedef RawFormat<MockTestImageCtx> MockRawFormat;
+ typedef SourceSpecBuilder<MockTestImageCtx> MockSourceSpecBuilder;
+
+ librbd::ImageCtx *m_image_ctx;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+
+ json_spirit::mObject stream_obj;
+ stream_obj["type"] = "file";
+ json_object["stream"] = stream_obj;
+ }
+
+ void expect_build_snapshot(MockSourceSpecBuilder& mock_source_spec_builder,
+ uint64_t index,
+ MockSnapshotInterface* mock_snapshot_interface,
+ int r) {
+ EXPECT_CALL(mock_source_spec_builder, build_snapshot(_, index, _))
+ .WillOnce(WithArgs<2>(Invoke([mock_snapshot_interface, r]
+ (std::shared_ptr<SnapshotInterface>* ptr) {
+ ptr->reset(mock_snapshot_interface);
+ return r;
+ })));
+ }
+
+ void expect_snapshot_open(MockSnapshotInterface& mock_snapshot_interface,
+ int r) {
+ EXPECT_CALL(mock_snapshot_interface, open(_, _))
+ .WillOnce(WithArg<1>(Invoke([r](Context* ctx) { ctx->complete(r); })));
+ }
+
+ void expect_snapshot_close(MockSnapshotInterface& mock_snapshot_interface,
+ int r) {
+ EXPECT_CALL(mock_snapshot_interface, close(_))
+ .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); }));
+ }
+
+ void expect_snapshot_get_info(MockSnapshotInterface& mock_snapshot_interface,
+ const SnapInfo& snap_info) {
+ EXPECT_CALL(mock_snapshot_interface, get_snap_info())
+ .WillOnce(ReturnRef(snap_info));
+ }
+
+ void expect_snapshot_read(MockSnapshotInterface& mock_snapshot_interface,
+ const io::Extents& image_extents,
+ const bufferlist& bl, int r) {
+ EXPECT_CALL(mock_snapshot_interface, read(_, image_extents, _))
+ .WillOnce(WithArgs<0, 2>(Invoke([bl, image_extents, r]
+ (io::AioCompletion* aio_comp, io::ReadResult& read_result) {
+ aio_comp->read_result = std::move(read_result);
+ aio_comp->read_result.set_image_extents(image_extents);
+ aio_comp->set_request_count(1);
+ auto ctx = new io::ReadResult::C_ImageReadRequest(aio_comp, 0,
+ image_extents);
+ ctx->bl = std::move(bl);
+ ctx->complete(r);
+ })));
+ }
+
+ void expect_snapshot_list_snap(MockSnapshotInterface& mock_snapshot_interface,
+ const io::Extents& image_extents,
+ const io::SparseExtents& sparse_extents,
+ int r) {
+ EXPECT_CALL(mock_snapshot_interface, list_snap(image_extents, _, _))
+ .WillOnce(WithArgs<1, 2>(Invoke(
+ [sparse_extents, r](io::SparseExtents* out_sparse_extents,
+ Context* ctx) {
+ out_sparse_extents->insert(sparse_extents);
+ ctx->complete(r);
+ })));
+ }
+
+ void expect_close(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.state, close(_))
+ .WillOnce(Invoke([r](Context* ctx) {
+ ctx->complete(r);
+ }));
+ }
+
+ json_spirit::mObject json_object;
+};
+
+TEST_F(TestMockMigrationRawFormat, OpenClose) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_snapshot_interface = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+ mock_snapshot_interface, 0);
+
+ expect_snapshot_open(*mock_snapshot_interface, 0);
+
+ expect_snapshot_close(*mock_snapshot_interface, 0);
+
+ MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_raw_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_raw_format.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationRawFormat, OpenError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_snapshot_interface = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+ mock_snapshot_interface, 0);
+
+ expect_snapshot_open(*mock_snapshot_interface, -ENOENT);
+
+ expect_snapshot_close(*mock_snapshot_interface, 0);
+ expect_close(mock_image_ctx, 0);
+
+ MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx;
+ mock_raw_format.open(&ctx);
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockMigrationRawFormat, OpenSnapshotError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_snapshot_interface_head = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+ mock_snapshot_interface_head, 0);
+
+ auto mock_snapshot_interface_1 = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, 1,
+ mock_snapshot_interface_1, 0);
+
+ expect_snapshot_open(*mock_snapshot_interface_1, -ENOENT);
+ expect_snapshot_open(*mock_snapshot_interface_head, 0);
+
+ expect_snapshot_close(*mock_snapshot_interface_1, 0);
+ expect_snapshot_close(*mock_snapshot_interface_head, 0);
+ expect_close(mock_image_ctx, 0);
+
+ json_spirit::mArray snapshots;
+ snapshots.push_back(json_spirit::mObject{});
+ json_object["snapshots"] = snapshots;
+
+ MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx;
+ mock_raw_format.open(&ctx);
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockMigrationRawFormat, GetSnapshots) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_snapshot_interface = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+ mock_snapshot_interface, 0);
+
+ expect_snapshot_open(*mock_snapshot_interface, 0);
+
+ expect_snapshot_close(*mock_snapshot_interface, 0);
+
+ MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_raw_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ FormatInterface::SnapInfos snap_infos;
+ mock_raw_format.get_snapshots(&snap_infos, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_TRUE(snap_infos.empty());
+
+ C_SaferCond ctx3;
+ mock_raw_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationRawFormat, GetImageSize) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_snapshot_interface = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+ mock_snapshot_interface, 0);
+
+ expect_snapshot_open(*mock_snapshot_interface, 0);
+
+ SnapInfo snap_info{{}, {}, 123, {}, 0, 0, {}};
+ expect_snapshot_get_info(*mock_snapshot_interface, snap_info);
+
+ expect_snapshot_close(*mock_snapshot_interface, 0);
+
+ MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_raw_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ uint64_t size;
+ mock_raw_format.get_image_size(CEPH_NOSNAP, &size, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(size, 123);
+
+ C_SaferCond ctx3;
+ mock_raw_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationRawFormat, GetImageSizeSnapshotDNE) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_snapshot_interface = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+ mock_snapshot_interface, 0);
+
+ expect_snapshot_open(*mock_snapshot_interface, 0);
+
+ expect_snapshot_close(*mock_snapshot_interface, 0);
+
+ MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_raw_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ uint64_t size;
+ mock_raw_format.get_image_size(0, &size, &ctx2);
+ ASSERT_EQ(-ENOENT, ctx2.wait());
+
+ C_SaferCond ctx3;
+ mock_raw_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationRawFormat, Read) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_snapshot_interface = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+ mock_snapshot_interface, 0);
+
+ expect_snapshot_open(*mock_snapshot_interface, 0);
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(123, '1'));
+ expect_snapshot_read(*mock_snapshot_interface, {{123, 123}}, expect_bl, 0);
+
+ expect_snapshot_close(*mock_snapshot_interface, 0);
+
+ MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_raw_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ auto aio_comp = io::AioCompletion::create_and_start(
+ &ctx2, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl;
+ io::ReadResult read_result{&bl};
+ ASSERT_TRUE(mock_raw_format.read(aio_comp, CEPH_NOSNAP, {{123, 123}},
+ std::move(read_result), 0, 0, {}));
+ ASSERT_EQ(123, ctx2.wait());
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ mock_raw_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationRawFormat, ListSnaps) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_snapshot_interface = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+ mock_snapshot_interface, 0);
+
+ expect_snapshot_open(*mock_snapshot_interface, 0);
+
+ SnapInfo snap_info{{}, {}, 123, {}, 0, 0, {}};
+ expect_snapshot_get_info(*mock_snapshot_interface, snap_info);
+ io::SparseExtents sparse_extents;
+ sparse_extents.insert(0, 123, {io::SPARSE_EXTENT_STATE_DATA, 123});
+ expect_snapshot_list_snap(*mock_snapshot_interface, {{0, 123}},
+ sparse_extents, 0);
+
+ expect_snapshot_close(*mock_snapshot_interface, 0);
+
+ MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_raw_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ io::SnapshotDelta snapshot_delta;
+ mock_raw_format.list_snaps({{0, 123}}, {CEPH_NOSNAP}, 0, &snapshot_delta, {},
+ &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+
+ io::SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{CEPH_NOSNAP, CEPH_NOSNAP}] = sparse_extents;
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+
+ C_SaferCond ctx3;
+ mock_raw_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationRawFormat, ListSnapsError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_snapshot_interface = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+ mock_snapshot_interface, 0);
+
+
+ expect_snapshot_open(*mock_snapshot_interface, 0);
+
+ SnapInfo snap_info{{}, {}, 123, {}, 0, 0, {}};
+ expect_snapshot_get_info(*mock_snapshot_interface, snap_info);
+ io::SparseExtents sparse_extents;
+ sparse_extents.insert(0, 123, {io::SPARSE_EXTENT_STATE_DATA, 123});
+ expect_snapshot_list_snap(*mock_snapshot_interface, {{0, 123}},
+ sparse_extents, -EINVAL);
+
+ expect_snapshot_close(*mock_snapshot_interface, 0);
+
+ MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_raw_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ io::SnapshotDelta snapshot_delta;
+ mock_raw_format.list_snaps({{0, 123}}, {CEPH_NOSNAP}, 0, &snapshot_delta, {},
+ &ctx2);
+ ASSERT_EQ(-EINVAL, ctx2.wait());
+
+ C_SaferCond ctx3;
+ mock_raw_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationRawFormat, ListSnapsMerge) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_snapshot_interface_head = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, CEPH_NOSNAP,
+ mock_snapshot_interface_head, 0);
+
+ auto mock_snapshot_interface_1 = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, 1,
+ mock_snapshot_interface_1, 0);
+
+ auto mock_snapshot_interface_2 = new MockSnapshotInterface();
+ expect_build_snapshot(mock_source_spec_builder, 2,
+ mock_snapshot_interface_2, 0);
+
+
+ expect_snapshot_open(*mock_snapshot_interface_1, 0);
+ expect_snapshot_open(*mock_snapshot_interface_2, 0);
+ expect_snapshot_open(*mock_snapshot_interface_head, 0);
+
+ SnapInfo snap_info_head{{}, {}, 256, {}, 0, 0, {}};
+ SnapInfo snap_info_1{snap_info_head};
+ snap_info_1.size = 123;
+ expect_snapshot_get_info(*mock_snapshot_interface_1, snap_info_1);
+ io::SparseExtents sparse_extents_1;
+ sparse_extents_1.insert(0, 123, {io::SPARSE_EXTENT_STATE_DATA, 123});
+ expect_snapshot_list_snap(*mock_snapshot_interface_1, {{0, 123}},
+ sparse_extents_1, 0);
+
+ SnapInfo snap_info_2{snap_info_head};
+ snap_info_2.size = 64;
+ expect_snapshot_get_info(*mock_snapshot_interface_2, snap_info_2);
+ io::SparseExtents sparse_extents_2;
+ sparse_extents_2.insert(0, 32, {io::SPARSE_EXTENT_STATE_DATA, 32});
+ expect_snapshot_list_snap(*mock_snapshot_interface_2, {{0, 123}},
+ sparse_extents_2, 0);
+
+ expect_snapshot_get_info(*mock_snapshot_interface_head, snap_info_head);
+ io::SparseExtents sparse_extents_head;
+ sparse_extents_head.insert(0, 16, {io::SPARSE_EXTENT_STATE_DATA, 16});
+ expect_snapshot_list_snap(*mock_snapshot_interface_head, {{0, 123}},
+ sparse_extents_head, 0);
+
+ expect_snapshot_close(*mock_snapshot_interface_1, 0);
+ expect_snapshot_close(*mock_snapshot_interface_2, 0);
+ expect_snapshot_close(*mock_snapshot_interface_head, 0);
+
+ json_spirit::mArray snapshots;
+ snapshots.push_back(json_spirit::mObject{});
+ snapshots.push_back(json_spirit::mObject{});
+ json_object["snapshots"] = snapshots;
+
+ MockRawFormat mock_raw_format(&mock_image_ctx, json_object,
+ &mock_source_spec_builder);
+
+ C_SaferCond ctx1;
+ mock_raw_format.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ io::SnapshotDelta snapshot_delta;
+ mock_raw_format.list_snaps({{0, 123}}, {1, CEPH_NOSNAP}, 0, &snapshot_delta,
+ {}, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+
+ io::SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{1, 1}] = sparse_extents_1;
+ sparse_extents_2.erase(0, 16);
+ sparse_extents_2.insert(64, 59, {io::SPARSE_EXTENT_STATE_ZEROED, 59});
+ expected_snapshot_delta[{CEPH_NOSNAP, 2}] = sparse_extents_2;
+ expected_snapshot_delta[{CEPH_NOSNAP, CEPH_NOSNAP}] = sparse_extents_head;
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+
+ C_SaferCond ctx3;
+ mock_raw_format.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+} // namespace migration
+} // namespace librbd
diff --git a/src/test/librbd/migration/test_mock_RawSnapshot.cc b/src/test/librbd/migration/test_mock_RawSnapshot.cc
new file mode 100644
index 000000000..3ce4b5c9d
--- /dev/null
+++ b/src/test/librbd/migration/test_mock_RawSnapshot.cc
@@ -0,0 +1,255 @@
+// -*- 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/migration/MockStreamInterface.h"
+#include "include/rbd_types.h"
+#include "common/ceph_mutex.h"
+#include "librbd/migration/FileStream.h"
+#include "librbd/migration/RawSnapshot.h"
+#include "librbd/migration/SourceSpecBuilder.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "json_spirit/json_spirit.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace migration {
+
+template<>
+struct SourceSpecBuilder<librbd::MockTestImageCtx> {
+
+ MOCK_CONST_METHOD2(build_stream, int(const json_spirit::mObject&,
+ std::shared_ptr<StreamInterface>*));
+
+};
+
+} // namespace migration
+} // namespace librbd
+
+#include "librbd/migration/RawSnapshot.cc"
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::WithArgs;
+
+namespace librbd {
+namespace migration {
+
+using ::testing::Invoke;
+
+class TestMockMigrationRawSnapshot : public TestMockFixture {
+public:
+ typedef RawSnapshot<MockTestImageCtx> MockRawSnapshot;
+ typedef SourceSpecBuilder<MockTestImageCtx> MockSourceSpecBuilder;
+
+ librbd::ImageCtx *m_image_ctx;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+
+ json_spirit::mObject stream_obj;
+ stream_obj["type"] = "file";
+ json_object["stream"] = stream_obj;
+ }
+
+ void expect_build_stream(MockSourceSpecBuilder& mock_source_spec_builder,
+ MockStreamInterface* mock_stream_interface, int r) {
+ EXPECT_CALL(mock_source_spec_builder, build_stream(_, _))
+ .WillOnce(WithArgs<1>(Invoke([mock_stream_interface, r]
+ (std::shared_ptr<StreamInterface>* ptr) {
+ ptr->reset(mock_stream_interface);
+ return r;
+ })));
+ }
+
+ void expect_stream_open(MockStreamInterface& mock_stream_interface, int r) {
+ EXPECT_CALL(mock_stream_interface, open(_))
+ .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); }));
+ }
+
+ void expect_stream_close(MockStreamInterface& mock_stream_interface, int r) {
+ EXPECT_CALL(mock_stream_interface, close(_))
+ .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); }));
+ }
+
+ void expect_stream_get_size(MockStreamInterface& mock_stream_interface,
+ uint64_t size, int r) {
+ EXPECT_CALL(mock_stream_interface, get_size(_, _))
+ .WillOnce(Invoke([size, r](uint64_t* out_size, Context* ctx) {
+ *out_size = size;
+ ctx->complete(r);
+ }));
+ }
+
+ void expect_stream_read(MockStreamInterface& mock_stream_interface,
+ const io::Extents& byte_extents,
+ const bufferlist& bl, int r) {
+ EXPECT_CALL(mock_stream_interface, read(byte_extents, _, _))
+ .WillOnce(WithArgs<1, 2>(Invoke([bl, r]
+ (bufferlist* out_bl, Context* ctx) {
+ *out_bl = bl;
+ ctx->complete(r);
+ })));
+ }
+
+ json_spirit::mObject json_object;
+};
+
+TEST_F(TestMockMigrationRawSnapshot, OpenClose) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+ expect_stream_get_size(*mock_stream_interface, 123, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ json_object["name"] = "snap1";
+ MockRawSnapshot mock_raw_snapshot(&mock_image_ctx, json_object,
+ &mock_source_spec_builder, 1);
+
+ C_SaferCond ctx1;
+ mock_raw_snapshot.open(nullptr, &ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ auto snap_info = mock_raw_snapshot.get_snap_info();
+ ASSERT_EQ("snap1", snap_info.name);
+ ASSERT_EQ(123, snap_info.size);
+
+ C_SaferCond ctx2;
+ mock_raw_snapshot.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationRawSnapshot, OpenError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, -ENOENT);
+
+ MockRawSnapshot mock_raw_snapshot(&mock_image_ctx, json_object,
+ &mock_source_spec_builder, 0);
+
+ C_SaferCond ctx;
+ mock_raw_snapshot.open(nullptr, &ctx);
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockMigrationRawSnapshot, GetSizeError) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+ expect_stream_get_size(*mock_stream_interface, 0, -EINVAL);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockRawSnapshot mock_raw_snapshot(&mock_image_ctx, json_object,
+ &mock_source_spec_builder, 0);
+
+ C_SaferCond ctx;
+ mock_raw_snapshot.open(nullptr, &ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMigrationRawSnapshot, Read) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+ expect_stream_get_size(*mock_stream_interface, 0, 0);
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(123, '1'));
+ expect_stream_read(*mock_stream_interface, {{123, 123}}, expect_bl, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockRawSnapshot mock_raw_snapshot(&mock_image_ctx, json_object,
+ &mock_source_spec_builder, 0);
+
+ C_SaferCond ctx1;
+ mock_raw_snapshot.open(nullptr, &ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ auto aio_comp = io::AioCompletion::create_and_start(
+ &ctx2, m_image_ctx, io::AIO_TYPE_READ);
+ bufferlist bl;
+ io::ReadResult read_result{&bl};
+ mock_raw_snapshot.read(aio_comp, {{123, 123}}, std::move(read_result), 0, 0,
+ {});
+ ASSERT_EQ(123, ctx2.wait());
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ mock_raw_snapshot.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationRawSnapshot, ListSnap) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+ MockSourceSpecBuilder mock_source_spec_builder;
+
+ auto mock_stream_interface = new MockStreamInterface();
+ expect_build_stream(mock_source_spec_builder, mock_stream_interface, 0);
+
+ expect_stream_open(*mock_stream_interface, 0);
+ expect_stream_get_size(*mock_stream_interface, 0, 0);
+
+ expect_stream_close(*mock_stream_interface, 0);
+
+ MockRawSnapshot mock_raw_snapshot(&mock_image_ctx, json_object,
+ &mock_source_spec_builder, 0);
+
+ C_SaferCond ctx1;
+ mock_raw_snapshot.open(nullptr, &ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ io::SparseExtents sparse_extents;
+ mock_raw_snapshot.list_snap({{0, 123}}, 0, &sparse_extents, {}, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+
+ C_SaferCond ctx3;
+ mock_raw_snapshot.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+} // namespace migration
+} // namespace librbd
diff --git a/src/test/librbd/migration/test_mock_S3Stream.cc b/src/test/librbd/migration/test_mock_S3Stream.cc
new file mode 100644
index 000000000..2f2097f79
--- /dev/null
+++ b/src/test/librbd/migration/test_mock_S3Stream.cc
@@ -0,0 +1,238 @@
+// -*- 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/migration/HttpClient.h"
+#include "librbd/migration/S3Stream.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "json_spirit/json_spirit.h"
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/beast/http.hpp>
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace migration {
+
+template <>
+struct HttpClient<MockTestImageCtx> {
+ static HttpClient* s_instance;
+ static HttpClient* create(MockTestImageCtx*, const std::string&) {
+ ceph_assert(s_instance != nullptr);
+ return s_instance;
+ }
+
+ HttpProcessorInterface* http_processor = nullptr;
+ void set_http_processor(HttpProcessorInterface* http_processor) {
+ this->http_processor = http_processor;
+ }
+
+ MOCK_METHOD1(open, void(Context*));
+ MOCK_METHOD1(close, void(Context*));
+ MOCK_METHOD2(get_size, void(uint64_t*, Context*));
+ MOCK_METHOD3(do_read, void(const io::Extents&, bufferlist*, Context*));
+ void read(io::Extents&& extents, bufferlist* bl, Context* ctx) {
+ do_read(extents, bl, ctx);
+ }
+
+ HttpClient() {
+ s_instance = this;
+ }
+};
+
+HttpClient<MockTestImageCtx>* HttpClient<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace migration
+} // namespace librbd
+
+#include "librbd/migration/S3Stream.cc"
+
+namespace librbd {
+namespace migration {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InSequence;
+using ::testing::WithArgs;
+
+class TestMockMigrationS3Stream : public TestMockFixture {
+public:
+ typedef S3Stream<MockTestImageCtx> MockS3Stream;
+ typedef HttpClient<MockTestImageCtx> MockHttpClient;
+
+ using EmptyBody = boost::beast::http::empty_body;
+ using EmptyRequest = boost::beast::http::request<EmptyBody>;
+
+ librbd::ImageCtx *m_image_ctx;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+ json_object["url"] = "http://some.site/bucket/file";
+ json_object["access_key"] = "0555b35654ad1656d804";
+ json_object["secret_key"] = "h7GhxuBLTrlhVUyxSPUKUV8r/2EI4ngqJxD7iBdBYLhwluN30JaT3Q==";
+ }
+
+ void expect_open(MockHttpClient& mock_http_client, int r) {
+ EXPECT_CALL(mock_http_client, open(_))
+ .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); }));
+ }
+
+ void expect_close(MockHttpClient& mock_http_client, int r) {
+ EXPECT_CALL(mock_http_client, close(_))
+ .WillOnce(Invoke([r](Context* ctx) { ctx->complete(r); }));
+ }
+
+ void expect_get_size(MockHttpClient& mock_http_client, uint64_t size, int r) {
+ EXPECT_CALL(mock_http_client, get_size(_, _))
+ .WillOnce(Invoke([size, r](uint64_t* out_size, Context* ctx) {
+ *out_size = size;
+ ctx->complete(r);
+ }));
+ }
+
+ void expect_read(MockHttpClient& mock_http_client, io::Extents byte_extents,
+ const bufferlist& bl, int r) {
+ uint64_t len = 0;
+ for (auto [_, byte_len] : byte_extents) {
+ len += byte_len;
+ }
+ EXPECT_CALL(mock_http_client, do_read(byte_extents, _, _))
+ .WillOnce(WithArgs<1, 2>(Invoke(
+ [len, bl, r](bufferlist* out_bl, Context* ctx) {
+ *out_bl = bl;
+ ctx->complete(r < 0 ? r : len);
+ })));
+ }
+
+ json_spirit::mObject json_object;
+};
+
+TEST_F(TestMockMigrationS3Stream, OpenClose) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ auto mock_http_client = new MockHttpClient();
+ expect_open(*mock_http_client, 0);
+
+ expect_close(*mock_http_client, 0);
+
+ MockS3Stream mock_http_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_http_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ mock_http_stream.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationS3Stream, GetSize) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ auto mock_http_client = new MockHttpClient();
+ expect_open(*mock_http_client, 0);
+
+ expect_get_size(*mock_http_client, 128, 0);
+
+ expect_close(*mock_http_client, 0);
+
+ MockS3Stream mock_http_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_http_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ uint64_t size;
+ mock_http_stream.get_size(&size, &ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+ ASSERT_EQ(128, size);
+
+ C_SaferCond ctx3;
+ mock_http_stream.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationS3Stream, Read) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ auto mock_http_client = new MockHttpClient();
+ expect_open(*mock_http_client, 0);
+
+ bufferlist expect_bl;
+ expect_bl.append(std::string(192, '1'));
+ expect_read(*mock_http_client, {{0, 128}, {256, 64}}, expect_bl, 0);
+
+ expect_close(*mock_http_client, 0);
+
+ MockS3Stream mock_http_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_http_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ bufferlist bl;
+ mock_http_stream.read({{0, 128}, {256, 64}}, &bl, &ctx2);
+ ASSERT_EQ(192, ctx2.wait());
+ ASSERT_EQ(expect_bl, bl);
+
+ C_SaferCond ctx3;
+ mock_http_stream.close(&ctx3);
+ ASSERT_EQ(0, ctx3.wait());
+}
+
+TEST_F(TestMockMigrationS3Stream, ProcessRequest) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ auto mock_http_client = new MockHttpClient();
+ expect_open(*mock_http_client, 0);
+
+ expect_close(*mock_http_client, 0);
+
+ MockS3Stream mock_http_stream(&mock_image_ctx, json_object);
+
+ C_SaferCond ctx1;
+ mock_http_stream.open(&ctx1);
+ ASSERT_EQ(0, ctx1.wait());
+
+ EmptyRequest request;
+ request.method(boost::beast::http::verb::get);
+ request.target("/bucket/resource");
+ mock_http_client->http_processor->process_request(request);
+
+ // basic test for date and known portion of authorization
+ ASSERT_EQ(1U, request.count(boost::beast::http::field::date));
+ ASSERT_EQ(1U, request.count(boost::beast::http::field::authorization));
+ ASSERT_TRUE(boost::algorithm::starts_with(
+ request[boost::beast::http::field::authorization],
+ "AWS 0555b35654ad1656d804:"));
+
+ C_SaferCond ctx2;
+ mock_http_stream.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+} // namespace migration
+} // namespace librbd
diff --git a/src/test/librbd/migration/test_mock_Utils.cc b/src/test/librbd/migration/test_mock_Utils.cc
new file mode 100644
index 000000000..917c191dd
--- /dev/null
+++ b/src/test/librbd/migration/test_mock_Utils.cc
@@ -0,0 +1,47 @@
+// -*- 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/migration/Utils.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+namespace librbd {
+namespace migration {
+namespace util {
+
+class TestMockMigrationUtils : public TestMockFixture {
+public:
+};
+
+TEST_F(TestMockMigrationUtils, ParseUrl) {
+ UrlSpec url_spec;
+ ASSERT_EQ(-EINVAL, parse_url(g_ceph_context, "", &url_spec));
+ ASSERT_EQ(-EINVAL, parse_url(g_ceph_context, "jttp://google.com/path",
+ &url_spec));
+ ASSERT_EQ(-EINVAL, parse_url(g_ceph_context, "http://google.com:absd/path",
+ &url_spec));
+
+ ASSERT_EQ(0, parse_url(g_ceph_context, "ceph.io/path", &url_spec));
+ ASSERT_EQ(UrlSpec(URL_SCHEME_HTTP, "ceph.io", "80", "/path"), url_spec);
+
+ ASSERT_EQ(0, parse_url(g_ceph_context, "http://google.com/path", &url_spec));
+ ASSERT_EQ(UrlSpec(URL_SCHEME_HTTP, "google.com", "80", "/path"), url_spec);
+
+ ASSERT_EQ(0, parse_url(g_ceph_context, "https://ceph.io/", &url_spec));
+ ASSERT_EQ(UrlSpec(URL_SCHEME_HTTPS, "ceph.io", "443", "/"), url_spec);
+
+ ASSERT_EQ(0, parse_url(g_ceph_context,
+ "http://google.com:1234/some/other/path", &url_spec));
+ ASSERT_EQ(UrlSpec(URL_SCHEME_HTTP, "google.com", "1234", "/some/other/path"),
+ url_spec);
+
+ ASSERT_EQ(0, parse_url(g_ceph_context,
+ "http://1.2.3.4/", &url_spec));
+ ASSERT_EQ(UrlSpec(URL_SCHEME_HTTP, "1.2.3.4", "80", "/"), url_spec);
+}
+
+} // namespace util
+} // namespace migration
+} // namespace librbd
diff --git a/src/test/librbd/mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc b/src/test/librbd/mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc
new file mode 100644
index 000000000..d7e70cab7
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc
@@ -0,0 +1,388 @@
+// -*- 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 "librbd/mirror/snapshot/CreateNonPrimaryRequest.h"
+#include "librbd/mirror/snapshot/Utils.h"
+#include "librbd/mirror/snapshot/WriteImageStateRequest.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace mirror {
+namespace snapshot {
+namespace util {
+
+namespace {
+
+struct Mock {
+ static Mock* s_instance;
+
+ Mock() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD1(can_create_non_primary_snapshot,
+ bool(librbd::MockTestImageCtx *));
+};
+
+Mock *Mock::s_instance = nullptr;
+
+} // anonymous namespace
+
+template<> bool can_create_non_primary_snapshot(
+ librbd::MockTestImageCtx *image_ctx) {
+ return Mock::s_instance->can_create_non_primary_snapshot(image_ctx);
+}
+
+} // namespace util
+
+template <>
+struct WriteImageStateRequest<MockTestImageCtx> {
+ uint64_t snap_id = CEPH_NOSNAP;
+ ImageState image_state;
+ Context* on_finish = nullptr;
+ static WriteImageStateRequest* s_instance;
+ static WriteImageStateRequest *create(MockTestImageCtx *image_ctx,
+ uint64_t snap_id,
+ const ImageState &image_state,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->snap_id = snap_id;
+ s_instance->image_state = image_state;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ WriteImageStateRequest() {
+ s_instance = this;
+ }
+};
+
+WriteImageStateRequest<MockTestImageCtx>* WriteImageStateRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/CreateNonPrimaryRequest.cc"
+template class librbd::mirror::snapshot::CreateNonPrimaryRequest<librbd::MockTestImageCtx>;
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorSnapshotCreateNonPrimaryRequest : public TestMockFixture {
+public:
+ typedef CreateNonPrimaryRequest<MockTestImageCtx> MockCreateNonPrimaryRequest;
+ typedef WriteImageStateRequest<MockTestImageCtx> MockWriteImageStateRequest;
+ typedef util::Mock MockUtils;
+
+ void expect_clone_md_ctx(MockTestImageCtx &mock_image_ctx) {
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), clone())
+ .WillOnce(Invoke([&mock_image_ctx]() {
+ get_mock_io_ctx(mock_image_ctx.md_ctx).get();
+ return &get_mock_io_ctx(mock_image_ctx.md_ctx);
+ }));
+ }
+
+ void expect_refresh_image(MockTestImageCtx &mock_image_ctx,
+ bool refresh_required, int r) {
+ EXPECT_CALL(*mock_image_ctx.state, is_refresh_required())
+ .WillOnce(Return(refresh_required));
+ if (refresh_required) {
+ EXPECT_CALL(*mock_image_ctx.state, refresh(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+ }
+
+ 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_can_create_non_primary_snapshot(MockUtils &mock_utils,
+ bool result) {
+ EXPECT_CALL(mock_utils, can_create_non_primary_snapshot(_))
+ .WillOnce(Return(result));
+ }
+
+ void expect_get_mirror_peers(MockTestImageCtx &mock_image_ctx,
+ const std::vector<cls::rbd::MirrorPeer> &peers,
+ int r) {
+ using ceph::encode;
+ bufferlist bl;
+ encode(peers, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_peer_list"),
+ _, _, _, _))
+ .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(bl)),
+ Return(r)));
+ }
+
+ void expect_create_snapshot(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.operations, snap_create(_, _, _, _, _))
+ .WillOnce(WithArg<4>(CompleteContext(
+ r, mock_image_ctx.image_ctx->op_work_queue)));
+ }
+
+ void expect_write_image_state(
+ MockTestImageCtx &mock_image_ctx,
+ MockWriteImageStateRequest &mock_write_image_state_request, int r) {
+ EXPECT_CALL(mock_image_ctx, get_snap_id(_, _))
+ .WillOnce(Return(123));
+ EXPECT_CALL(mock_write_image_state_request, send())
+ .WillOnce(Invoke([&mock_image_ctx, &mock_write_image_state_request, r]() {
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ mock_write_image_state_request.on_finish, r);
+ }));
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, Success) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, true, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, true);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockWriteImageStateRequest mock_write_image_state_request;
+ expect_write_image_state(mock_image_ctx, mock_write_image_state_request, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, SuccessDemoted) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ expect_refresh_image(mock_image_ctx, true, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockWriteImageStateRequest mock_write_image_state_request;
+ expect_write_image_state(mock_image_ctx, mock_write_image_state_request, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, true,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, RefreshError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, true, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, GetMirrorImageError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, false, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, CanNotError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, false, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, false);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, GetMirrorPeersError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ expect_refresh_image(mock_image_ctx, true, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, true);
+ expect_get_mirror_peers(mock_image_ctx, {}, -EPERM);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, true,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, CreateSnapshotError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, true, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, true);
+ expect_create_snapshot(mock_image_ctx, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, WriteImageStateError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, true, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, true);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockWriteImageStateRequest mock_write_image_state_request;
+ expect_write_image_state(mock_image_ctx, mock_write_image_state_request,
+ -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
diff --git a/src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc b/src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc
new file mode 100644
index 000000000..bf0363486
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.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 "include/stringify.h"
+#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 "librbd/mirror/snapshot/CreatePrimaryRequest.h"
+#include "librbd/mirror/snapshot/UnlinkPeerRequest.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace mirror {
+namespace snapshot {
+namespace util {
+
+namespace {
+
+struct Mock {
+ static Mock* s_instance;
+
+ Mock() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD4(can_create_primary_snapshot,
+ bool(librbd::MockTestImageCtx *, bool, bool, uint64_t *));
+};
+
+Mock *Mock::s_instance = nullptr;
+
+} // anonymous namespace
+
+template<> bool can_create_primary_snapshot(librbd::MockTestImageCtx *image_ctx,
+ bool demoted, bool force,
+ bool* requires_orphan,
+ uint64_t *rollback_snap_id) {
+ return Mock::s_instance->can_create_primary_snapshot(image_ctx, demoted,
+ force, rollback_snap_id);
+}
+
+} // namespace util
+
+template <>
+struct UnlinkPeerRequest<MockTestImageCtx> {
+ uint64_t snap_id = CEPH_NOSNAP;
+ std::string mirror_peer_uuid;
+ Context* on_finish = nullptr;
+ static UnlinkPeerRequest* s_instance;
+ static UnlinkPeerRequest *create(MockTestImageCtx *image_ctx,
+ uint64_t snap_id,
+ const std::string &mirror_peer_uuid,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->snap_id = snap_id;
+ s_instance->mirror_peer_uuid = mirror_peer_uuid;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ UnlinkPeerRequest() {
+ s_instance = this;
+ }
+};
+
+UnlinkPeerRequest<MockTestImageCtx>* UnlinkPeerRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/CreatePrimaryRequest.cc"
+template class librbd::mirror::snapshot::CreatePrimaryRequest<librbd::MockTestImageCtx>;
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorSnapshotCreatePrimaryRequest : public TestMockFixture {
+public:
+ typedef CreatePrimaryRequest<MockTestImageCtx> MockCreatePrimaryRequest;
+ typedef UnlinkPeerRequest<MockTestImageCtx> MockUnlinkPeerRequest;
+ typedef util::Mock MockUtils;
+
+ uint64_t m_snap_seq = 0;
+
+ void snap_create(MockTestImageCtx &mock_image_ctx,
+ const cls::rbd::SnapshotNamespace &ns,
+ const std::string& snap_name) {
+ ASSERT_TRUE(mock_image_ctx.snap_info.insert(
+ {m_snap_seq++,
+ SnapInfo{snap_name, ns, 0, {}, 0, 0, {}}}).second);
+ }
+
+ void expect_clone_md_ctx(MockTestImageCtx &mock_image_ctx) {
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), clone())
+ .WillOnce(Invoke([&mock_image_ctx]() {
+ get_mock_io_ctx(mock_image_ctx.md_ctx).get();
+ return &get_mock_io_ctx(mock_image_ctx.md_ctx);
+ }));
+ }
+
+ void expect_can_create_primary_snapshot(MockUtils &mock_utils, bool demoted,
+ bool force, bool result) {
+ EXPECT_CALL(mock_utils,
+ can_create_primary_snapshot(_, demoted, force, nullptr))
+ .WillOnce(Return(result));
+ }
+
+ void expect_get_mirror_peers(MockTestImageCtx &mock_image_ctx,
+ const std::vector<cls::rbd::MirrorPeer> &peers,
+ int r) {
+ using ceph::encode;
+ bufferlist bl;
+ encode(peers, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_peer_list"),
+ _, _, _, _))
+ .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(bl)),
+ Return(r)));
+ }
+
+ void expect_create_snapshot(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.operations, snap_create(_, _, _, _, _))
+ .WillOnce(DoAll(
+ Invoke([this, &mock_image_ctx, r](
+ const cls::rbd::SnapshotNamespace &ns,
+ const std::string& snap_name,
+ uint64_t flags,
+ ProgressContext &prog_ctx,
+ Context *on_finish) {
+ if (r != 0) {
+ return;
+ }
+ snap_create(mock_image_ctx, ns, snap_name);
+ }),
+ WithArg<4>(CompleteContext(
+ r, mock_image_ctx.image_ctx->op_work_queue))
+ ));
+ }
+
+ void expect_unlink_peer(MockTestImageCtx &mock_image_ctx,
+ MockUnlinkPeerRequest &mock_unlink_peer_request,
+ uint64_t snap_id, const std::string &peer_uuid,
+ bool is_linked, int r) {
+ EXPECT_CALL(mock_unlink_peer_request, send())
+ .WillOnce(Invoke([&mock_image_ctx, &mock_unlink_peer_request,
+ snap_id, peer_uuid, is_linked, r]() {
+ ASSERT_EQ(mock_unlink_peer_request.mirror_peer_uuid,
+ peer_uuid);
+ ASSERT_EQ(mock_unlink_peer_request.snap_id, snap_id);
+ if (r == 0) {
+ auto it = mock_image_ctx.snap_info.find(snap_id);
+ ASSERT_NE(it, mock_image_ctx.snap_info.end());
+ auto info =
+ boost::get<cls::rbd::MirrorSnapshotNamespace>(
+ &it->second.snap_namespace);
+ ASSERT_NE(nullptr, info);
+ ASSERT_EQ(is_linked, info->mirror_peer_uuids.erase(
+ peer_uuid));
+ if (info->mirror_peer_uuids.empty()) {
+ mock_image_ctx.snap_info.erase(it);
+ }
+ }
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ mock_unlink_peer_request.on_finish, r);
+ }));
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, Success) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, CanNotError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, false);
+
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, GetMirrorPeersError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, CreateSnapshotError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, SuccessUnlinkPeer) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->config.set_val("rbd_mirroring_max_mirroring_snapshots", "3");
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ for (int i = 0; i < 3; i++) {
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"uuid"}, "", CEPH_NOSNAP};
+ snap_create(mock_image_ctx, ns, "mirror_snap");
+ }
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ auto it = mock_image_ctx.snap_info.rbegin();
+ auto snap_id = it->first;
+ expect_unlink_peer(mock_image_ctx, mock_unlink_peer_request, snap_id, "uuid",
+ true, 0);
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, SuccessUnlinkNoPeer) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->config.set_val("rbd_mirroring_max_mirroring_snapshots", "3");
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {}, "", CEPH_NOSNAP};
+ snap_create(mock_image_ctx, ns, "mirror_snap");
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ auto it = mock_image_ctx.snap_info.rbegin();
+ auto snap_id = it->first;
+ std::list<string> peer_uuids = {"uuid"};
+ expect_unlink_peer(mock_image_ctx, mock_unlink_peer_request, snap_id, "uuid",
+ false, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, SuccessUnlinkMultiplePeers) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->config.set_val("rbd_mirroring_max_mirroring_snapshots", "3");
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ for (int i = 0; i < 3; i++) {
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"uuid1", "uuid2"}, "",
+ CEPH_NOSNAP};
+ snap_create(mock_image_ctx, ns, "mirror_snap");
+ }
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid1", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"},
+ {"uuid2", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ auto it = mock_image_ctx.snap_info.rbegin();
+ auto snap_id = it->first;
+ expect_unlink_peer(mock_image_ctx, mock_unlink_peer_request, snap_id, "uuid1",
+ true, 0);
+ expect_unlink_peer(mock_image_ctx, mock_unlink_peer_request, snap_id, "uuid2",
+ true, 0);
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
diff --git a/src/test/librbd/mirror/snapshot/test_mock_ImageMeta.cc b/src/test/librbd/mirror/snapshot/test_mock_ImageMeta.cc
new file mode 100644
index 000000000..70a5eaeb0
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_ImageMeta.cc
@@ -0,0 +1,159 @@
+// -*- 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 "librbd/ImageState.h"
+#include "librbd/mirror/snapshot/ImageMeta.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+#include "librbd/mirror/snapshot/ImageMeta.cc"
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorSnapshotImageMeta : public TestMockFixture {
+public:
+ typedef ImageMeta<MockTestImageCtx> MockImageMeta;
+
+ void expect_metadata_get(MockTestImageCtx& mock_image_ctx,
+ const std::string& mirror_uuid,
+ const std::string& value, int r) {
+ bufferlist in_bl;
+ ceph::encode(util::get_image_meta_key(mirror_uuid), in_bl);
+
+ bufferlist out_bl;
+ ceph::encode(value, out_bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, StrEq("rbd"),
+ StrEq("metadata_get"), ContentsEqual(in_bl), _, _, _))
+ .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(out_bl)),
+ Return(r)));
+ }
+
+ void expect_metadata_set(MockTestImageCtx& mock_image_ctx,
+ const std::string& mirror_uuid,
+ const std::string& value, int r) {
+ bufferlist value_bl;
+ value_bl.append(value);
+
+ bufferlist in_bl;
+ ceph::encode(
+ std::map<std::string, bufferlist>{
+ {util::get_image_meta_key(mirror_uuid), value_bl}},
+ 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(TestMockMirrorSnapshotImageMeta, Load) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_get(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": true}", 0);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ C_SaferCond ctx;
+ mock_image_meta.load(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, LoadError) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_get(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": true}", -EINVAL);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ C_SaferCond ctx;
+ mock_image_meta.load(&ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, LoadCorrupt) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_get(mock_image_ctx, "mirror uuid",
+ "\"resync_requested\": true}", 0);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ C_SaferCond ctx;
+ mock_image_meta.load(&ctx);
+ ASSERT_EQ(-EBADMSG, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, Save) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_set(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": true}", 0);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ mock_image_meta.resync_requested = true;
+
+ C_SaferCond ctx;
+ mock_image_meta.save(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ // should have sent image-update notification
+ ASSERT_TRUE(image_ctx->state->is_refresh_required());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, SaveError) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_set(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": false}", -EINVAL);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+
+ C_SaferCond ctx;
+ mock_image_meta.save(&ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
diff --git a/src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc b/src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc
new file mode 100644
index 000000000..af9c34933
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc
@@ -0,0 +1,389 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/stringify.h"
+#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 "librbd/image/ListWatchersRequest.h"
+#include "librbd/mirror/snapshot/CreateNonPrimaryRequest.h"
+#include "librbd/mirror/snapshot/CreatePrimaryRequest.h"
+#include "librbd/mirror/snapshot/PromoteRequest.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace image {
+
+template <>
+struct ListWatchersRequest<MockTestImageCtx> {
+ std::list<obj_watch_t> *watchers;
+ Context* on_finish = nullptr;
+ static ListWatchersRequest* s_instance;
+ static ListWatchersRequest *create(MockTestImageCtx &image_ctx, int flags,
+ std::list<obj_watch_t> *watchers,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->watchers = watchers;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ ListWatchersRequest() {
+ s_instance = this;
+ }
+};
+
+ListWatchersRequest<MockTestImageCtx>* ListWatchersRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace image
+
+namespace mirror {
+namespace snapshot {
+namespace util {
+
+namespace {
+
+struct Mock {
+ static Mock* s_instance;
+
+ Mock() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD5(can_create_primary_snapshot,
+ bool(librbd::MockTestImageCtx *, bool, bool, bool*, uint64_t *));
+};
+
+Mock *Mock::s_instance = nullptr;
+
+} // anonymous namespace
+
+template<> bool can_create_primary_snapshot(librbd::MockTestImageCtx *image_ctx,
+ bool demoted, bool force,
+ bool* requires_orphan,
+ uint64_t *rollback_snap_id) {
+ return Mock::s_instance->can_create_primary_snapshot(image_ctx, demoted,
+ force, requires_orphan,
+ rollback_snap_id);
+}
+
+} // namespace util
+
+template <>
+struct CreateNonPrimaryRequest<MockTestImageCtx> {
+ std::string primary_mirror_uuid;
+ uint64_t primary_snap_id = CEPH_NOSNAP;
+ Context* on_finish = nullptr;
+ static CreateNonPrimaryRequest* s_instance;
+ static CreateNonPrimaryRequest *create(MockTestImageCtx *image_ctx,
+ bool demoted,
+ const std::string &primary_mirror_uuid,
+ uint64_t primary_snap_id,
+ SnapSeqs snap_seqs,
+ const ImageState &image_state,
+ uint64_t *snap_id,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->primary_mirror_uuid = primary_mirror_uuid;
+ s_instance->primary_snap_id = primary_snap_id;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ CreateNonPrimaryRequest() {
+ s_instance = this;
+ }
+};
+
+CreateNonPrimaryRequest<MockTestImageCtx>* CreateNonPrimaryRequest<MockTestImageCtx>::s_instance = nullptr;
+
+template <>
+struct CreatePrimaryRequest<MockTestImageCtx> {
+ bool demoted = false;
+ bool force = false;
+ Context* on_finish = nullptr;
+ static CreatePrimaryRequest* s_instance;
+ static CreatePrimaryRequest *create(MockTestImageCtx *image_ctx,
+ const std::string& global_image_id,
+ uint64_t clean_since_snap_id,
+ uint64_t snap_create_flags,
+ uint32_t flags, uint64_t *snap_id,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->demoted = ((flags & CREATE_PRIMARY_FLAG_DEMOTED) != 0);
+ s_instance->force = ((flags & CREATE_PRIMARY_FLAG_FORCE) != 0);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ CreatePrimaryRequest() {
+ s_instance = this;
+ }
+};
+
+CreatePrimaryRequest<MockTestImageCtx>* CreatePrimaryRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/PromoteRequest.cc"
+template class librbd::mirror::snapshot::PromoteRequest<librbd::MockTestImageCtx>;
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+
+class TestMockMirrorSnapshotPromoteRequest : public TestMockFixture {
+public:
+ typedef librbd::image::ListWatchersRequest<MockTestImageCtx> MockListWatchersRequest;
+ typedef PromoteRequest<MockTestImageCtx> MockPromoteRequest;
+ typedef CreateNonPrimaryRequest<MockTestImageCtx> MockCreateNonPrimaryRequest;
+ typedef CreatePrimaryRequest<MockTestImageCtx> MockCreatePrimaryRequest;
+ typedef util::Mock MockUtils;
+
+ void expect_can_create_primary_snapshot(MockUtils &mock_utils, bool force,
+ bool requires_orphan,
+ uint64_t rollback_snap_id,
+ bool result) {
+ EXPECT_CALL(mock_utils,
+ can_create_primary_snapshot(_, false, force, _, _))
+ .WillOnce(DoAll(
+ WithArgs<3,4 >(Invoke(
+ [requires_orphan, rollback_snap_id]
+ (bool* orphan, uint64_t *snap_id) {
+ *orphan = requires_orphan;
+ *snap_id = rollback_snap_id;
+ })),
+ Return(result)));
+ }
+
+ void expect_create_orphan_snapshot(
+ MockTestImageCtx &mock_image_ctx,
+ MockCreateNonPrimaryRequest &mock_create_non_primary_request, int r) {
+ EXPECT_CALL(mock_create_non_primary_request, send())
+ .WillOnce(
+ Invoke([&mock_image_ctx, &mock_create_non_primary_request, r]() {
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ mock_create_non_primary_request.on_finish, r);
+ }));
+ }
+
+ void expect_list_watchers(
+ MockTestImageCtx &mock_image_ctx,
+ MockListWatchersRequest &mock_list_watchers_request,
+ const std::list<obj_watch_t> &watchers, int r) {
+ EXPECT_CALL(mock_list_watchers_request, send())
+ .WillOnce(
+ Invoke([&mock_image_ctx, &mock_list_watchers_request, watchers, r]() {
+ *mock_list_watchers_request.watchers = watchers;
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ mock_list_watchers_request.on_finish, r);
+ }));
+ }
+
+ void expect_acquire_lock(MockTestImageCtx &mock_image_ctx, int r) {
+ if (mock_image_ctx.exclusive_lock == nullptr) {
+ return;
+ }
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner())
+ .WillOnce(Return(false));
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, block_requests(_));
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, acquire_lock(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ if (r == 0) {
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner())
+ .WillOnce(Return(true));
+ }
+ }
+
+ void expect_get_snap_info(MockTestImageCtx &mock_image_ctx,
+ uint64_t snap_id, const SnapInfo* snap_info) {
+ EXPECT_CALL(mock_image_ctx, get_snap_info(snap_id))
+ .WillOnce(Return(snap_info));
+ }
+
+ void expect_rollback(MockTestImageCtx &mock_image_ctx, uint64_t snap_id,
+ const SnapInfo* snap_info, int r) {
+ expect_get_snap_info(mock_image_ctx, snap_id, snap_info);
+ EXPECT_CALL(*mock_image_ctx.operations,
+ execute_snap_rollback(snap_info->snap_namespace,
+ snap_info->name, _, _))
+ .WillOnce(WithArg<3>(CompleteContext(
+ r, mock_image_ctx.image_ctx->op_work_queue)));
+ }
+
+ void expect_create_promote_snapshot(
+ MockTestImageCtx &mock_image_ctx,
+ MockCreatePrimaryRequest &mock_create_primary_request, int r) {
+ EXPECT_CALL(mock_create_primary_request, send())
+ .WillOnce(
+ Invoke([&mock_image_ctx, &mock_create_primary_request, r]() {
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ mock_create_primary_request.on_finish, r);
+ }));
+ }
+
+ void expect_release_lock(MockTestImageCtx &mock_image_ctx, int r) {
+ if (mock_image_ctx.exclusive_lock == nullptr) {
+ return;
+ }
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, unblock_requests());
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, release_lock(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, Success) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, true, false, CEPH_NOSNAP,
+ true);
+ MockCreatePrimaryRequest mock_create_primary_request;
+ expect_create_promote_snapshot(mock_image_ctx, mock_create_primary_request,
+ 0);
+ C_SaferCond ctx;
+ auto req = new MockPromoteRequest(&mock_image_ctx, "gid", &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, SuccessForce) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_op_work_queue(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ }
+
+ InSequence seq;
+
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, true, true, CEPH_NOSNAP, true);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_orphan_snapshot(mock_image_ctx, mock_create_non_primary_request,
+ 0);
+ MockListWatchersRequest mock_list_watchers_request;
+ expect_list_watchers(mock_image_ctx, mock_list_watchers_request, {}, 0);
+ expect_acquire_lock(mock_image_ctx, 0);
+
+ SnapInfo snap_info = {"snap", cls::rbd::MirrorSnapshotNamespace{}, 0,
+ {}, 0, 0, {}};
+ MockCreatePrimaryRequest mock_create_primary_request;
+ expect_create_promote_snapshot(mock_image_ctx, mock_create_primary_request,
+ 0);
+ expect_release_lock(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockPromoteRequest(&mock_image_ctx, "gid", &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, SuccessRollback) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_op_work_queue(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ }
+
+ InSequence seq;
+
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, true, false, 123, true);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_orphan_snapshot(mock_image_ctx, mock_create_non_primary_request,
+ 0);
+ MockListWatchersRequest mock_list_watchers_request;
+ expect_list_watchers(mock_image_ctx, mock_list_watchers_request, {}, 0);
+ expect_acquire_lock(mock_image_ctx, 0);
+
+ SnapInfo snap_info = {"snap", cls::rbd::MirrorSnapshotNamespace{}, 0,
+ {}, 0, 0, {}};
+ expect_rollback(mock_image_ctx, 123, &snap_info, 0);
+ MockCreatePrimaryRequest mock_create_primary_request;
+ expect_create_promote_snapshot(mock_image_ctx, mock_create_primary_request,
+ 0);
+ expect_release_lock(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockPromoteRequest(&mock_image_ctx, "gid", &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, ErrorCannotRollback) {
+ REQUIRE_FORMAT_V2();
+
+ 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;
+
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, true, false, CEPH_NOSNAP,
+ false);
+
+ C_SaferCond ctx;
+ auto req = new MockPromoteRequest(&mock_image_ctx, "gid", &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
diff --git a/src/test/librbd/mirror/snapshot/test_mock_UnlinkPeerRequest.cc b/src/test/librbd/mirror/snapshot/test_mock_UnlinkPeerRequest.cc
new file mode 100644
index 000000000..456b6ccdc
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_UnlinkPeerRequest.cc
@@ -0,0 +1,385 @@
+// -*- 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 "librbd/mirror/snapshot/UnlinkPeerRequest.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/UnlinkPeerRequest.cc"
+template class librbd::mirror::snapshot::UnlinkPeerRequest<librbd::MockTestImageCtx>;
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+
+class TestMockMirrorSnapshotUnlinkPeerRequest : public TestMockFixture {
+public:
+ typedef UnlinkPeerRequest<MockTestImageCtx> MockUnlinkPeerRequest;
+
+ uint64_t m_snap_seq = 0;
+
+ uint64_t snap_create(MockTestImageCtx &mock_image_ctx,
+ const cls::rbd::SnapshotNamespace &ns,
+ const std::string& snap_name) {
+ EXPECT_TRUE(mock_image_ctx.snap_info.insert(
+ {++m_snap_seq,
+ SnapInfo{snap_name, ns, 0, {}, 0, 0, {}}}).second);
+ return m_snap_seq;
+ }
+
+ void expect_get_snap_info(MockTestImageCtx &mock_image_ctx,
+ librados::snap_t snap_id) {
+ EXPECT_CALL(mock_image_ctx, get_snap_info(snap_id))
+ .WillRepeatedly(Invoke([&mock_image_ctx](
+ librados::snap_t snap_id) -> librbd::SnapInfo * {
+ auto it = mock_image_ctx.snap_info.find(snap_id);
+ if (it == mock_image_ctx.snap_info.end()) {
+ return nullptr;
+ }
+ return &it->second;
+ }));
+ }
+
+ void expect_is_refresh_required(MockTestImageCtx &mock_image_ctx,
+ bool refresh_required) {
+ EXPECT_CALL(*mock_image_ctx.state, is_refresh_required())
+ .WillOnce(Return(refresh_required));
+ }
+
+ void expect_refresh_image(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.state, refresh(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+
+ void expect_unlink_peer(MockTestImageCtx &mock_image_ctx, uint64_t snap_id,
+ const std::string &peer_uuid, int r) {
+ using ceph::encode;
+ bufferlist bl;
+ encode(snapid_t{snap_id}, bl);
+ encode(peer_uuid, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, StrEq("rbd"),
+ StrEq("mirror_image_snapshot_unlink_peer"),
+ ContentsEqual(bl), _, _, _))
+ .WillOnce(Invoke([&mock_image_ctx, snap_id, peer_uuid, r](auto&&... args) -> int {
+ if (r == 0) {
+ auto it = mock_image_ctx.snap_info.find(snap_id);
+ EXPECT_NE(it, mock_image_ctx.snap_info.end());
+ auto info =
+ boost::get<cls::rbd::MirrorSnapshotNamespace>(
+ &it->second.snap_namespace);
+ EXPECT_NE(nullptr, info);
+ EXPECT_NE(0, info->mirror_peer_uuids.erase(
+ peer_uuid));
+ }
+ return r;
+ }));
+ }
+
+ void expect_notify_update(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(mock_image_ctx, notify_update(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+
+ void expect_remove_snapshot(MockTestImageCtx &mock_image_ctx,
+ uint64_t snap_id, int r) {
+ EXPECT_CALL(*mock_image_ctx.operations, snap_remove(_, _, _))
+ .WillOnce(Invoke([&mock_image_ctx, snap_id, r](
+ const cls::rbd::SnapshotNamespace &snap_namespace,
+ const std::string &snap_name, Context *on_finish) {
+ if (r == 0) {
+ auto it = mock_image_ctx.snap_info.find(snap_id);
+ EXPECT_NE(it, mock_image_ctx.snap_info.end());
+ EXPECT_EQ(it->second.snap_namespace, snap_namespace);
+ EXPECT_EQ(it->second.name, snap_name);
+ mock_image_ctx.snap_info.erase(it);
+ }
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ on_finish, r);
+ }));
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, Success) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer1_uuid", "peer2_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, 0);
+ expect_unlink_peer(mock_image_ctx, snap_id, "peer1_uuid", 0);
+ expect_notify_update(mock_image_ctx, 0);
+ expect_refresh_image(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer1_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, RemoveSnapshot) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+ snap_create(mock_image_ctx, ns, "mirror_snap2");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, 0);
+ expect_remove_snapshot(mock_image_ctx, snap_id, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, SnapshotRemoveEmptyPeers) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+ ns.mirror_peer_uuids = {"peer_uuid"};
+ snap_create(mock_image_ctx, ns, "mirror_snap2");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, 0);
+ expect_remove_snapshot(mock_image_ctx, snap_id, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, SnapshotDNE) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ expect_get_snap_info(mock_image_ctx, 123);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, 123, "peer_uuid", &ctx);
+ req->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, PeerDNE) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "unknown_peer",
+ &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, InvalidSnapshot) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::UserSnapshotNamespace ns;
+ auto snap_id = snap_create(mock_image_ctx, ns, "user_snap");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, false);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, RefreshError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, 123, "peer_uuid", &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, UnlinkError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer1_uuid", "peer2_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, false);
+ expect_unlink_peer(mock_image_ctx, snap_id, "peer1_uuid", -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer1_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, NotifyError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer1_uuid", "peer2_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, false);
+ expect_unlink_peer(mock_image_ctx, snap_id, "peer1_uuid", 0);
+ expect_notify_update(mock_image_ctx, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer1_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, RemoveSnapshotError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+ snap_create(mock_image_ctx, ns, "mirror_snap2");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, false);
+ expect_remove_snapshot(mock_image_ctx, snap_id, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
diff --git a/src/test/librbd/mirror/snapshot/test_mock_Utils.cc b/src/test/librbd/mirror/snapshot/test_mock_Utils.cc
new file mode 100644
index 000000000..410dc3eba
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_Utils.cc
@@ -0,0 +1,177 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/stringify.h"
+#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 "librbd/mirror/snapshot/UnlinkPeerRequest.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/Utils.cc"
+template bool librbd::mirror::snapshot::util::can_create_primary_snapshot(
+ librbd::MockTestImageCtx *image_ctx, bool demoted, bool force,
+ bool* requires_orphan, uint64_t *rollback_snap_id);
+template bool librbd::mirror::snapshot::util::can_create_non_primary_snapshot(
+ librbd::MockTestImageCtx *image_ctx);
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorSnapshotUtils : public TestMockFixture {
+public:
+ uint64_t m_snap_seq = 0;
+
+ uint64_t snap_create(MockTestImageCtx &mock_image_ctx,
+ const cls::rbd::SnapshotNamespace &ns,
+ const std::string& snap_name) {
+ EXPECT_TRUE(mock_image_ctx.snap_info.insert(
+ {++m_snap_seq,
+ SnapInfo{snap_name, ns, 0, {}, 0, 0, {}}}).second);
+ return m_snap_seq;
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotUtils, CanCreatePrimarySnapshot) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ // no previous mirror snapshots found
+ bool requires_orphan;
+ uint64_t rollback_snap_id;
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, false,
+ &requires_orphan,
+ &rollback_snap_id));
+ ASSERT_FALSE(requires_orphan);
+ ASSERT_EQ(rollback_snap_id, CEPH_NOSNAP);
+
+ cls::rbd::MirrorSnapshotNamespace nns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "mirror_uuid", 123};
+ nns.complete = true;
+ auto copied_snap_id = snap_create(mock_image_ctx, nns, "NPS1");
+
+ // without force, previous snapshot is non-primary
+ ASSERT_FALSE(util::can_create_primary_snapshot(&mock_image_ctx, false, false,
+ nullptr, nullptr));
+
+ // demoted, previous snapshot is non-primary
+ ASSERT_FALSE(util::can_create_primary_snapshot(&mock_image_ctx, true, true,
+ nullptr, nullptr));
+
+ // previous non-primary snapshot is copied
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, true,
+ &requires_orphan,
+ &rollback_snap_id));
+ ASSERT_TRUE(requires_orphan);
+ ASSERT_EQ(rollback_snap_id, CEPH_NOSNAP);
+
+ nns.complete = false;
+ snap_create(mock_image_ctx, nns, "NPS2");
+
+ // previous non-primary snapshot is not copied yet
+ ASSERT_FALSE(util::can_create_primary_snapshot(&mock_image_ctx, false, true,
+ nullptr, nullptr));
+
+ // can rollback
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, true,
+ nullptr, &rollback_snap_id));
+ ASSERT_EQ(rollback_snap_id, copied_snap_id);
+
+ nns.state = cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED;
+ snap_create(mock_image_ctx, nns, "NPS3");
+
+ // previous non-primary snapshot is orphan
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, true,
+ nullptr, nullptr));
+
+ cls::rbd::MirrorSnapshotNamespace pns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED, {"uuid"}, "", CEPH_NOSNAP};
+ snap_create(mock_image_ctx, pns, "PS1");
+
+ // previous primary snapshot is demoted, no force
+ ASSERT_FALSE(util::can_create_primary_snapshot(&mock_image_ctx, false, false,
+ nullptr, nullptr));
+
+ // previous primary snapshot is demoted, force
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, true,
+ nullptr, nullptr));
+
+ pns.state = cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY;
+ snap_create(mock_image_ctx, pns, "PS2");
+
+ // previous snapshot is not demoted primary
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, false,
+ nullptr, nullptr));
+}
+
+TEST_F(TestMockMirrorSnapshotUtils, CanCreateNonPrimarySnapshot) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ // no previous mirror snapshots found
+ ASSERT_TRUE(util::can_create_non_primary_snapshot(&mock_image_ctx));
+
+ cls::rbd::MirrorSnapshotNamespace nns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "mirror_uuid", 123};
+ snap_create(mock_image_ctx, nns, "NPS1");
+
+ // previous non-primary snapshot is not copied yet
+ ASSERT_FALSE(util::can_create_non_primary_snapshot(&mock_image_ctx));
+
+ nns.complete = true;
+ snap_create(mock_image_ctx, nns, "NPS2");
+
+ // previous non-primary snapshot is copied
+ ASSERT_TRUE(util::can_create_non_primary_snapshot(&mock_image_ctx));
+
+ cls::rbd::MirrorSnapshotNamespace pns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"uuid"}, "", CEPH_NOSNAP};
+ snap_create(mock_image_ctx, pns, "PS1");
+
+ // previous primary snapshot is not in demoted state
+ ASSERT_FALSE(util::can_create_non_primary_snapshot(&mock_image_ctx));
+
+ pns.state = cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED;
+ snap_create(mock_image_ctx, pns, "PS2");
+
+ // previous primary snapshot is in demoted state
+ ASSERT_TRUE(util::can_create_non_primary_snapshot(&mock_image_ctx));
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // 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 000000000..823884fe5
--- /dev/null
+++ b/src/test/librbd/mirror/test_mock_DisableRequest.cc
@@ -0,0 +1,694 @@
+// -*- 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/MockOperations.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "librbd/journal/PromoteRequest.h"
+#include "librbd/mirror/DisableRequest.h"
+#include "librbd/mirror/GetInfoRequest.h"
+#include "librbd/mirror/ImageRemoveRequest.h"
+#include "librbd/mirror/ImageStateUpdateRequest.h"
+#include "librbd/mirror/snapshot/PromoteRequest.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+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 mirror {
+template <>
+struct GetInfoRequest<librbd::MockTestImageCtx> {
+ cls::rbd::MirrorImage *mirror_image;
+ PromotionState *promotion_state;
+ Context *on_finish = nullptr;
+ static GetInfoRequest *s_instance;
+ static GetInfoRequest *create(librbd::MockTestImageCtx &,
+ cls::rbd::MirrorImage *mirror_image,
+ PromotionState *promotion_state,
+ std::string* primary_mirror_uuid,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->mirror_image = mirror_image;
+ s_instance->promotion_state = promotion_state;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ GetInfoRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+template <>
+struct ImageRemoveRequest<librbd::MockTestImageCtx> {
+ static ImageRemoveRequest* s_instance;
+ Context* on_finish = nullptr;
+
+ static ImageRemoveRequest* create(
+ librados::IoCtx& io_ctx,
+ const std::string& global_image_id,
+ const std::string& image_id,
+ Context* on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ ImageRemoveRequest() {
+ s_instance = this;
+ }
+};
+
+template <>
+struct ImageStateUpdateRequest<librbd::MockTestImageCtx> {
+ static ImageStateUpdateRequest* s_instance;
+ Context* on_finish = nullptr;
+
+ static ImageStateUpdateRequest* create(
+ librados::IoCtx& io_ctx,
+ const std::string& image_id,
+ cls::rbd::MirrorImageState mirror_image_state,
+ const cls::rbd::MirrorImage& mirror_image,
+ Context* on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ ImageStateUpdateRequest() {
+ s_instance = this;
+ }
+};
+
+GetInfoRequest<librbd::MockTestImageCtx> *GetInfoRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+ImageRemoveRequest<librbd::MockTestImageCtx> *ImageRemoveRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+ImageStateUpdateRequest<librbd::MockTestImageCtx> *ImageStateUpdateRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+namespace snapshot {
+
+template <>
+struct PromoteRequest<librbd::MockTestImageCtx> {
+ Context *on_finish = nullptr;
+ static PromoteRequest *s_instance;
+ static PromoteRequest *create(librbd::MockTestImageCtx*,
+ const std::string& global_image_id,
+ 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 snapshot
+} // namespace mirror
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/DisableRequest.cc"
+template class librbd::mirror::DisableRequest<librbd::MockTestImageCtx>;
+
+ACTION_P(TestFeatures, image_ctx) {
+ return ((image_ctx->features & arg0) != 0);
+}
+
+namespace librbd {
+namespace mirror {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorDisableRequest : public TestMockFixture {
+public:
+ typedef DisableRequest<MockTestImageCtx> MockDisableRequest;
+ typedef Journal<MockTestImageCtx> MockJournal;
+ typedef journal::PromoteRequest<MockTestImageCtx> MockJournalPromoteRequest;
+ typedef mirror::GetInfoRequest<MockTestImageCtx> MockGetInfoRequest;
+ typedef mirror::ImageRemoveRequest<MockTestImageCtx> MockImageRemoveRequest;
+ typedef mirror::ImageStateUpdateRequest<MockTestImageCtx> MockImageStateUpdateRequest;
+ typedef mirror::snapshot::PromoteRequest<MockTestImageCtx> MockSnapshotPromoteRequest;
+
+ void expect_get_mirror_info(MockTestImageCtx &mock_image_ctx,
+ MockGetInfoRequest &mock_get_info_request,
+ const cls::rbd::MirrorImage &mirror_image,
+ PromotionState promotion_state, int r) {
+
+ EXPECT_CALL(mock_get_info_request, send())
+ .WillOnce(
+ Invoke([&mock_image_ctx, &mock_get_info_request, mirror_image,
+ promotion_state, r]() {
+ if (r == 0) {
+ *mock_get_info_request.mirror_image = mirror_image;
+ *mock_get_info_request.promotion_state = promotion_state;
+ }
+ mock_image_ctx.op_work_queue->queue(
+ mock_get_info_request.on_finish, r);
+ }));
+ }
+
+ void expect_mirror_image_state_update(
+ MockTestImageCtx &mock_image_ctx,
+ MockImageStateUpdateRequest& mock_request, int r) {
+ EXPECT_CALL(mock_request, send())
+ .WillOnce(
+ Invoke([&mock_image_ctx, &mock_request, r]() {
+ mock_image_ctx.op_work_queue->queue(mock_request.on_finish, r);
+ }));
+ }
+
+ void expect_mirror_image_remove(
+ MockTestImageCtx &mock_image_ctx,
+ MockImageRemoveRequest& mock_request, int r) {
+ EXPECT_CALL(mock_request, send())
+ .WillOnce(
+ Invoke([&mock_image_ctx, &mock_request, r]() {
+ mock_image_ctx.op_work_queue->queue(mock_request.on_finish, r);
+ }));
+ }
+
+ 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,
+ MockJournalPromoteRequest &mock_promote_request,
+ int r) {
+ EXPECT_CALL(mock_promote_request, send())
+ .WillOnce(FinishRequest(&mock_promote_request, r, &mock_image_ctx));
+ }
+
+ void expect_snapshot_promote(MockTestImageCtx &mock_image_ctx,
+ MockSnapshotPromoteRequest &mock_promote_request,
+ int r) {
+ EXPECT_CALL(mock_promote_request, send())
+ .WillOnce(FinishRequest(&mock_promote_request, r, &mock_image_ctx));
+ }
+
+ void expect_is_refresh_required(MockTestImageCtx &mock_image_ctx,
+ bool refresh_required) {
+ EXPECT_CALL(*mock_image_ctx.state, is_refresh_required())
+ .WillOnce(Return(refresh_required));
+ }
+
+ void expect_refresh_image(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.state, refresh(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+
+ 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);
+
+ 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;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_mirror_image_state_update(
+ mock_image_ctx, mock_image_state_update_request, 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);
+ expect_journal_client_unregister(mock_image_ctx, "peer 2", 0);
+ expect_journal_client_list(mock_image_ctx, {}, 0);
+ MockImageRemoveRequest mock_image_remove_request;
+ expect_mirror_image_remove(
+ mock_image_ctx, mock_image_remove_request, 0);
+
+ 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);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_mirror_image_state_update(
+ mock_image_ctx, mock_image_state_update_request, 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);
+ MockJournalPromoteRequest mock_promote_request;
+
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_NON_PRIMARY, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_mirror_image_state_update(
+ mock_image_ctx, mock_image_state_update_request, 0);
+ expect_journal_promote(mock_image_ctx, mock_promote_request, 0);
+ expect_is_refresh_required(mock_image_ctx, false);
+ expect_journal_client_list(mock_image_ctx, {}, 0);
+ MockImageRemoveRequest mock_image_remove_request;
+ expect_mirror_image_remove(
+ mock_image_ctx, mock_image_remove_request, 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);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_NON_PRIMARY, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDisableRequest(&mock_image_ctx, false, false, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, GetMirrorInfoError) {
+ REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+ 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;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, 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);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_mirror_image_state_update(
+ mock_image_ctx, mock_image_state_update_request, -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) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockJournalPromoteRequest mock_promote_request;
+
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_NON_PRIMARY, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_mirror_image_state_update(
+ mock_image_ctx, mock_image_state_update_request, 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);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_mirror_image_state_update(
+ mock_image_ctx, mock_image_state_update_request, 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);
+
+ 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;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_mirror_image_state_update(
+ mock_image_ctx, mock_image_state_update_request, 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);
+
+ 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;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_mirror_image_state_update(
+ mock_image_ctx, mock_image_state_update_request, 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, SnapshotPromoteError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSnapshotPromoteRequest mock_promote_request;
+
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_NON_PRIMARY, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_mirror_image_state_update(
+ mock_image_ctx, mock_image_state_update_request, 0);
+ expect_snapshot_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, RefreshError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockSnapshotPromoteRequest mock_promote_request;
+
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_NON_PRIMARY, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_mirror_image_state_update(
+ mock_image_ctx, mock_image_state_update_request, 0);
+ expect_snapshot_promote(mock_image_ctx, mock_promote_request, 0);
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, -EPERM);
+
+ C_SaferCond ctx;
+ auto req = new MockDisableRequest(&mock_image_ctx, true, true, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, 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);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ MockGetInfoRequest mock_get_info_request;
+ expect_get_mirror_info(
+ mock_image_ctx, mock_get_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_mirror_image_state_update(
+ mock_image_ctx, mock_image_state_update_request, 0);
+ expect_journal_client_list(mock_image_ctx, {}, 0);
+ MockImageRemoveRequest mock_image_remove_request;
+ expect_mirror_image_remove(
+ mock_image_ctx, mock_image_remove_request, -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 000000000..f900b627d
--- /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 000000000..db675abf0
--- /dev/null
+++ b/src/test/librbd/mock/MockExclusiveLock.h
@@ -0,0 +1,50 @@
+// -*- 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 "common/RefCountedObj.h"
+#include "include/int_types.h"
+#include "include/rados/librados.hpp"
+#include "librbd/exclusive_lock/Policy.h"
+#include "librbd/io/Types.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_METHOD3(set_require_lock, void(bool init_shutdown, io::Direction,
+ Context*));
+ MOCK_METHOD1(unset_require_lock, void(io::Direction));
+
+ MOCK_METHOD1(start_op, Context*(int*));
+
+ void get() {}
+ void put() {}
+};
+
+} // 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 000000000..743b9c633
--- /dev/null
+++ b/src/test/librbd/mock/MockImageCtx.cc
@@ -0,0 +1,56 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/neorados/RADOS.hpp"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockSafeTimer.h"
+#include "librbd/io/AsyncOperation.h"
+
+static MockSafeTimer *s_timer;
+static ceph::mutex *s_timer_lock;
+
+namespace librbd {
+
+MockImageCtx* MockImageCtx::s_instance = nullptr;
+
+void MockImageCtx::set_timer_instance(MockSafeTimer *timer,
+ ceph::mutex *timer_lock) {
+ s_timer = timer;
+ s_timer_lock = timer_lock;
+}
+
+void MockImageCtx::get_timer_instance(CephContext *cct, MockSafeTimer **timer,
+ ceph::mutex **timer_lock) {
+ *timer = s_timer;
+ *timer_lock = s_timer_lock;
+}
+
+void MockImageCtx::wait_for_async_ops() {
+ io::AsyncOperation async_op;
+ async_op.start_op(*image_ctx);
+
+ C_SaferCond ctx;
+ async_op.flush(&ctx);
+ ctx.wait();
+
+ async_op.finish_op();
+}
+
+IOContext MockImageCtx::get_data_io_context() {
+ auto ctx = std::make_shared<neorados::IOContext>(
+ data_ctx.get_id(), data_ctx.get_namespace());
+ if (snap_id != CEPH_NOSNAP) {
+ ctx->read_snap(snap_id);
+ }
+ if (!snapc.snaps.empty()) {
+ ctx->write_snap_context(
+ {{snapc.seq, {snapc.snaps.begin(), snapc.snaps.end()}}});
+ }
+ return ctx;
+}
+
+IOContext MockImageCtx::duplicate_data_io_context() {
+ return std::make_shared<neorados::IOContext>(*get_data_io_context());
+}
+
+} // namespace librbd
diff --git a/src/test/librbd/mock/MockImageCtx.h b/src/test/librbd/mock/MockImageCtx.h
new file mode 100644
index 000000000..521ecff0c
--- /dev/null
+++ b/src/test/librbd/mock/MockImageCtx.h
@@ -0,0 +1,343 @@
+// -*- 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/MockPluginRegistry.h"
+#include "test/librbd/mock/MockReadahead.h"
+#include "test/librbd/mock/io/MockImageDispatcher.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>
+
+class MockSafeTimer;
+
+namespace librbd {
+
+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;
+ }
+
+ 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),
+ read_only_flags(image_ctx.read_only_flags),
+ read_only_mask(image_ctx.read_only_mask),
+ 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),
+ asio_engine(image_ctx.asio_engine),
+ rados_api(image_ctx.rados_api),
+ owner_lock(image_ctx.owner_lock),
+ image_lock(image_ctx.image_lock),
+ timestamp_lock(image_ctx.timestamp_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_image_dispatcher(new io::MockImageDispatcher()),
+ io_object_dispatcher(new io::MockObjectDispatcher()),
+ op_work_queue(new MockContextWQ()),
+ plugin_registry(new MockPluginRegistry()),
+ 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),
+ read_flags(image_ctx.read_flags),
+ ignore_migrating(image_ctx.ignore_migrating),
+ enable_sparse_copyup(image_ctx.enable_sparse_copyup),
+ 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();
+ }
+ }
+
+ virtual ~MockImageCtx() {
+ wait_for_async_requests();
+ wait_for_async_ops();
+ 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 plugin_registry;
+ delete io_image_dispatcher;
+ delete io_object_dispatcher;
+ }
+
+ void wait_for_async_ops();
+ 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_effective_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 ceph::shared_mutex &in_image_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_METHOD1(set_exclusive_lock_policy, void(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_METHOD0(rebuild_data_io_context, void());
+ IOContext get_data_io_context();
+ IOContext duplicate_data_io_context();
+
+ static void set_timer_instance(MockSafeTimer *timer, ceph::mutex *timer_lock);
+ static void get_timer_instance(CephContext *cct, MockSafeTimer **timer,
+ ceph::mutex **timer_lock);
+
+ 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<ImageCtx::SnapKey, librados::snap_t, ImageCtx::SnapKeyComparator> snap_ids;
+
+ bool old_format;
+ bool read_only;
+ uint32_t read_only_flags;
+ uint32_t read_only_mask;
+
+ 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;
+
+ std::shared_ptr<AsioEngine> asio_engine;
+ neorados::RADOS& rados_api;
+
+ librados::IoCtx md_ctx;
+ librados::IoCtx data_ctx;
+
+ ceph::shared_mutex &owner_lock;
+ ceph::shared_mutex &image_lock;
+ ceph::shared_mutex &timestamp_lock;
+ ceph::mutex &async_ops_lock;
+ ceph::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::MockImageDispatcher *io_image_dispatcher;
+ io::MockObjectDispatcher *io_object_dispatcher;
+ MockContextWQ *op_work_queue;
+
+ MockPluginRegistry* plugin_registry;
+
+ MockReadahead readahead;
+ uint64_t readahead_max_bytes;
+
+ EventSocket &event_socket;
+
+ MockImageCtx *child = nullptr;
+ MockImageCtx *parent;
+ MockOperations *operations;
+ MockImageState *state;
+
+ MockImageWatcher *image_watcher;
+ MockObjectMap *object_map;
+ MockExclusiveLock *exclusive_lock;
+ MockJournal *journal;
+
+ ZTracer::Endpoint trace_endpoint;
+
+ crypto::CryptoInterface* crypto = nullptr;
+
+ 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;
+ uint32_t read_flags;
+ bool ignore_migrating;
+ bool enable_sparse_copyup;
+ 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 000000000..f510c4a0f
--- /dev/null
+++ b/src/test/librbd/mock/MockImageState.h
@@ -0,0 +1,39 @@
+// -*- 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 {
+
+class UpdateWatchCtx;
+
+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_METHOD2(register_update_watcher, int(UpdateWatchCtx *, uint64_t *));
+ MOCK_METHOD2(unregister_update_watcher, void(uint64_t, Context *));
+
+ 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 000000000..c44238ae1
--- /dev/null
+++ b/src/test/librbd/mock/MockImageWatcher.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_WATCHER_H
+#define CEPH_TEST_LIBRBD_MOCK_IMAGE_WATCHER_H
+
+#include "gmock/gmock.h"
+
+class Context;
+
+namespace librbd {
+
+class ProgressContext;
+
+struct MockImageWatcher {
+ MOCK_METHOD0(is_registered, bool());
+ MOCK_METHOD0(is_unregistered, bool());
+ MOCK_METHOD0(is_blocklisted, 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());
+
+ MOCK_METHOD3(notify_quiesce, void(uint64_t *, ProgressContext &, Context *));
+ MOCK_METHOD2(notify_unquiesce, void(uint64_t, Context *));
+};
+
+} // 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 000000000..97feb0187
--- /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 000000000..15baf7903
--- /dev/null
+++ b/src/test/librbd/mock/MockJournal.h
@@ -0,0 +1,96 @@
+// -*- 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 "common/RefCountedObj.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;
+ }
+
+ void get() {}
+ void put() {}
+
+ 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 000000000..33bb252ac
--- /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 000000000..d72408403
--- /dev/null
+++ b/src/test/librbd/mock/MockObjectMap.h
@@ -0,0 +1,73 @@
+// -*- 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));
+
+ void get() {}
+ void put() {}
+
+ 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 000000000..99dc253d1
--- /dev/null
+++ b/src/test/librbd/mock/MockOperations.h
@@ -0,0 +1,72 @@
+// -*- 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_METHOD5(snap_create, void(const cls::rbd::SnapshotNamespace &snapshot_namespace,
+ const std::string &snap_name,
+ uint64_t flags,
+ ProgressContext &prog_ctx,
+ Context *on_finish));
+ MOCK_METHOD6(execute_snap_create, void(const cls::rbd::SnapshotNamespace &snapshot_namespace,
+ const std::string &snap_name,
+ Context *on_finish,
+ uint64_t journal_op_tid,
+ uint64_t flags,
+ ProgressContext &prog_ctx));
+ 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/MockPluginRegistry.h b/src/test/librbd/mock/MockPluginRegistry.h
new file mode 100644
index 000000000..8854a7426
--- /dev/null
+++ b/src/test/librbd/mock/MockPluginRegistry.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_PLUGIN_REGISTRY_H
+#define CEPH_TEST_LIBRBD_MOCK_PLUGIN_REGISTRY_H
+
+#include <gmock/gmock.h>
+
+class Context;
+
+namespace librbd {
+
+struct MockPluginRegistry{
+ MOCK_METHOD2(init, void(const std::string&, Context*));
+ MOCK_METHOD1(acquired_exclusive_lock, void(Context*));
+ MOCK_METHOD1(prerelease_exclusive_lock, void(Context*));
+};
+
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_PLUGIN_REGISTRY_H
diff --git a/src/test/librbd/mock/MockReadahead.h b/src/test/librbd/mock/MockReadahead.h
new file mode 100644
index 000000000..40fceaa7a
--- /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/MockSafeTimer.h b/src/test/librbd/mock/MockSafeTimer.h
new file mode 100644
index 000000000..9f6be1960
--- /dev/null
+++ b/src/test/librbd/mock/MockSafeTimer.h
@@ -0,0 +1,20 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_MOCK_SAFE_TIMER_H
+#define CEPH_MOCK_SAFE_TIMER_H
+
+#include <gmock/gmock.h>
+
+struct Context;
+
+struct MockSafeTimer {
+ virtual ~MockSafeTimer() {
+ }
+
+ MOCK_METHOD2(add_event_after, Context*(double, Context *));
+ MOCK_METHOD2(add_event_at, Context*(ceph::real_clock::time_point, Context *));
+ MOCK_METHOD1(cancel_event, bool(Context *));
+};
+
+#endif // CEPH_MOCK_SAFE_TIMER_H
diff --git a/src/test/librbd/mock/cache/MockImageCache.h b/src/test/librbd/mock/cache/MockImageCache.h
new file mode 100644
index 000000000..c9a3cc335
--- /dev/null
+++ b/src/test/librbd/mock/cache/MockImageCache.h
@@ -0,0 +1,58 @@
+// -*- 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 "librbd/io/Types.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_METHOD2(aio_flush, void(librbd::io::FlushSource, 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/crypto/MockCryptoInterface.h b/src/test/librbd/mock/crypto/MockCryptoInterface.h
new file mode 100644
index 000000000..027ebde62
--- /dev/null
+++ b/src/test/librbd/mock/crypto/MockCryptoInterface.h
@@ -0,0 +1,33 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_CRYPTO_INTERFACE_H
+#define CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_CRYPTO_INTERFACE_H
+
+#include "include/buffer.h"
+#include "gmock/gmock.h"
+#include "librbd/crypto/CryptoInterface.h"
+
+namespace librbd {
+namespace crypto {
+
+struct MockCryptoInterface : CryptoInterface {
+
+ MOCK_METHOD2(encrypt, int(ceph::bufferlist*, uint64_t));
+ MOCK_METHOD2(decrypt, int(ceph::bufferlist*, uint64_t));
+ MOCK_CONST_METHOD0(get_key, const unsigned char*());
+ MOCK_CONST_METHOD0(get_key_length, int());
+
+ uint64_t get_block_size() const override {
+ return 4096;
+ }
+
+ uint64_t get_data_offset() const override {
+ return 4 * 1024 * 1024;
+ }
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_CRYPTO_INTERFACE_H
diff --git a/src/test/librbd/mock/crypto/MockDataCryptor.h b/src/test/librbd/mock/crypto/MockDataCryptor.h
new file mode 100644
index 000000000..51a738862
--- /dev/null
+++ b/src/test/librbd/mock/crypto/MockDataCryptor.h
@@ -0,0 +1,43 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_DATA_CRYPTOR_H
+#define CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_DATA_CRYPTOR_H
+
+#include "gmock/gmock.h"
+#include "librbd/crypto/DataCryptor.h"
+
+namespace librbd {
+namespace crypto {
+
+struct MockCryptoContext {};
+
+class MockDataCryptor : public DataCryptor<MockCryptoContext> {
+
+public:
+ uint32_t block_size = 16;
+ uint32_t iv_size = 16;
+
+ uint32_t get_block_size() const override {
+ return block_size;
+ }
+
+ uint32_t get_iv_size() const override {
+ return iv_size;
+ }
+
+ MOCK_METHOD1(get_context, MockCryptoContext*(CipherMode));
+ MOCK_METHOD2(return_context, void(MockCryptoContext*, CipherMode));
+ MOCK_CONST_METHOD3(init_context, int(MockCryptoContext*,
+ const unsigned char*, uint32_t));
+ MOCK_CONST_METHOD4(update_context, int(MockCryptoContext*,
+ const unsigned char*, unsigned char*,
+ uint32_t));
+ MOCK_CONST_METHOD0(get_key, const unsigned char*());
+ MOCK_CONST_METHOD0(get_key_length, int());
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_DATA_CRYPTOR_H
diff --git a/src/test/librbd/mock/crypto/MockEncryptionFormat.h b/src/test/librbd/mock/crypto/MockEncryptionFormat.h
new file mode 100644
index 000000000..1caf99eb0
--- /dev/null
+++ b/src/test/librbd/mock/crypto/MockEncryptionFormat.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_CRYPTO_MOCK_ENCRYPTION_FORMAT_H
+#define CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_ENCRYPTION_FORMAT_H
+
+#include "gmock/gmock.h"
+#include "librbd/crypto/EncryptionFormat.h"
+#include "test/librbd/mock/MockImageCtx.h"
+
+namespace librbd {
+namespace crypto {
+
+struct MockEncryptionFormat : EncryptionFormat<MockImageCtx> {
+
+ MOCK_METHOD2(format, void(MockImageCtx* ictx, Context* on_finish));
+ MOCK_METHOD2(load, void(MockImageCtx* ictx, Context* on_finish));
+ MOCK_METHOD0(get_crypto, ceph::ref_t<CryptoInterface>());
+};
+
+} // namespace crypto
+
+namespace api {
+namespace util {
+
+inline int create_encryption_format(
+ CephContext* cct, encryption_format_t format,
+ encryption_options_t opts, size_t opts_size, bool c_api,
+ crypto::EncryptionFormat<MockImageCtx>** result_format) {
+ if (opts == nullptr) {
+ return -ENOTSUP;
+ }
+ *result_format = (crypto::MockEncryptionFormat*)opts;
+ return 0;
+}
+
+} // namespace util
+} // namespace api
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_ENCRYPTION_FORMAT_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 000000000..f49eeb23f
--- /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/MockImageDispatch.h b/src/test/librbd/mock/io/MockImageDispatch.h
new file mode 100644
index 000000000..02dff3487
--- /dev/null
+++ b/src/test/librbd/mock/io/MockImageDispatch.h
@@ -0,0 +1,99 @@
+// -*- 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_DISPATCH_H
+#define CEPH_TEST_LIBRBD_MOCK_IO_IMAGE_DISPATCH_H
+
+#include "gmock/gmock.h"
+#include "include/Context.h"
+#include "librbd/io/ImageDispatchInterface.h"
+#include "librbd/io/Types.h"
+
+class Context;
+
+namespace librbd {
+namespace io {
+
+struct MockImageDispatch : public ImageDispatchInterface {
+public:
+ MOCK_CONST_METHOD0(get_dispatch_layer, ImageDispatchLayer());
+
+ MOCK_METHOD1(shut_down, void(Context*));
+
+ bool read(
+ AioCompletion* aio_comp, Extents &&image_extents,
+ ReadResult &&read_result, IOContext io_context, int op_flags,
+ int read_flags, const ZTracer::Trace &parent_trace, uint64_t tid,
+ std::atomic<uint32_t>* image_dispatch_flags,
+ DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool write(
+ AioCompletion* aio_comp, Extents &&image_extents, bufferlist &&bl,
+ IOContext io_context, int op_flags, const ZTracer::Trace &parent_trace,
+ uint64_t tid, std::atomic<uint32_t>* image_dispatch_flags,
+ DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool discard(
+ AioCompletion* aio_comp, Extents &&image_extents,
+ uint32_t discard_granularity_bytes, IOContext io_context,
+ const ZTracer::Trace &parent_trace, uint64_t tid,
+ std::atomic<uint32_t>* image_dispatch_flags,
+ DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool write_same(
+ AioCompletion* aio_comp, Extents &&image_extents, bufferlist &&bl,
+ IOContext io_context, int op_flags, const ZTracer::Trace &parent_trace,
+ uint64_t tid, std::atomic<uint32_t>* image_dispatch_flags,
+ DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool compare_and_write(
+ AioCompletion* aio_comp, Extents &&image_extents, bufferlist &&cmp_bl,
+ bufferlist &&bl, uint64_t *mismatch_offset, IOContext io_context,
+ int op_flags, const ZTracer::Trace &parent_trace, uint64_t tid,
+ std::atomic<uint32_t>* image_dispatch_flags,
+ DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool flush(
+ AioCompletion* aio_comp, FlushSource flush_source,
+ const ZTracer::Trace &parent_trace, uint64_t tid,
+ std::atomic<uint32_t>* image_dispatch_flags,
+ DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool list_snaps(
+ AioCompletion* aio_comp, Extents&& image_extents, SnapIds&& snap_ids,
+ int list_snaps_flags, SnapshotDelta* snapshot_delta,
+ const ZTracer::Trace &parent_trace, uint64_t tid,
+ std::atomic<uint32_t>* image_dispatch_flags,
+ DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool invalidate_cache(Context* on_finish) override {
+ return false;
+ }
+
+};
+
+} // namespace io
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_IO_IMAGE_DISPATCH_H
diff --git a/src/test/librbd/mock/io/MockImageDispatcher.h b/src/test/librbd/mock/io/MockImageDispatcher.h
new file mode 100644
index 000000000..9e95f1d5c
--- /dev/null
+++ b/src/test/librbd/mock/io/MockImageDispatcher.h
@@ -0,0 +1,48 @@
+// -*- 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_DISPATCHER_H
+#define CEPH_TEST_LIBRBD_MOCK_IO_IMAGE_DISPATCHER_H
+
+#include "gmock/gmock.h"
+#include "include/Context.h"
+#include "librbd/io/ImageDispatcher.h"
+#include "librbd/io/ImageDispatchSpec.h"
+#include "librbd/io/Types.h"
+
+class Context;
+
+namespace librbd {
+namespace io {
+
+struct ImageDispatchInterface;
+
+struct MockImageDispatcher : public ImageDispatcherInterface {
+public:
+ MOCK_METHOD1(shut_down, void(Context*));
+
+ MOCK_METHOD1(register_dispatch, void(ImageDispatchInterface*));
+ MOCK_METHOD1(exists, bool(ImageDispatchLayer));
+ MOCK_METHOD2(shut_down_dispatch, void(ImageDispatchLayer, Context*));
+ MOCK_METHOD1(invalidate_cache, void(Context *));
+
+ MOCK_METHOD1(send, void(ImageDispatchSpec*));
+ MOCK_METHOD3(finish, void(int r, ImageDispatchLayer, uint64_t));
+
+ MOCK_METHOD1(apply_qos_schedule_tick_min, void(uint64_t));
+ MOCK_METHOD4(apply_qos_limit, void(uint64_t, uint64_t, uint64_t, uint64_t));
+
+ MOCK_CONST_METHOD0(writes_blocked, bool());
+ MOCK_METHOD0(block_writes, int());
+ MOCK_METHOD1(block_writes, void(Context*));
+
+ MOCK_METHOD0(unblock_writes, void());
+ MOCK_METHOD1(wait_on_writes_unblocked, void(Context*));
+
+ MOCK_METHOD2(remap_extents, void(Extents&, ImageExtentsMapType));
+};
+
+} // namespace io
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_IO_IMAGE_DISPATCHER_H
diff --git a/src/test/librbd/mock/io/MockObjectDispatch.h b/src/test/librbd/mock/io/MockObjectDispatch.h
new file mode 100644
index 000000000..baf86d9fb
--- /dev/null
+++ b/src/test/librbd/mock/io/MockObjectDispatch.h
@@ -0,0 +1,137 @@
+// -*- 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_dispatch_layer, ObjectDispatchLayer());
+
+ MOCK_METHOD1(shut_down, void(Context*));
+
+ MOCK_METHOD6(execute_read,
+ bool(uint64_t, ReadExtents*, IOContext io_context, uint64_t*,
+ DispatchResult*, Context*));
+ bool read(
+ uint64_t object_no, ReadExtents* extents, IOContext io_context,
+ int op_flags, int read_flags, const ZTracer::Trace& parent_trace,
+ uint64_t* version, int* dispatch_flags,
+ DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) {
+ return execute_read(object_no, extents, io_context, version,
+ dispatch_result, on_dispatched);
+ }
+
+ MOCK_METHOD9(execute_discard,
+ bool(uint64_t, uint64_t, uint64_t, IOContext, int,
+ int*, uint64_t*, DispatchResult*, Context*));
+ bool discard(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ IOContext io_context, 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, io_context,
+ discard_flags, dispatch_flags, journal_tid,
+ dispatch_result, on_dispatched);
+ }
+
+ MOCK_METHOD10(execute_write,
+ bool(uint64_t, uint64_t, const ceph::bufferlist&,
+ IOContext, int, std::optional<uint64_t>, int*,
+ uint64_t*, DispatchResult*, Context *));
+ bool write(
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
+ IOContext io_context, int op_flags, int write_flags,
+ std::optional<uint64_t> assert_version,
+ 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, io_context, write_flags,
+ assert_version, dispatch_flags, journal_tid,
+ dispatch_result, on_dispatched);
+ }
+
+ MOCK_METHOD10(execute_write_same,
+ bool(uint64_t, uint64_t, uint64_t,
+ const LightweightBufferExtents&,
+ const ceph::bufferlist&, IOContext, int*,
+ uint64_t*, DispatchResult*, Context *));
+ bool write_same(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ LightweightBufferExtents&& buffer_extents, ceph::bufferlist&& data,
+ IOContext io_context, 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, io_context, 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(
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& cmp_data,
+ ceph::bufferlist&& write_data, IOContext io_context, 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_METHOD4(execute_flush, bool(FlushSource, uint64_t*, DispatchResult*,
+ Context*));
+ bool flush(FlushSource flush_source, const ZTracer::Trace &parent_trace,
+ uint64_t* journal_tid, DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) {
+ return execute_flush(flush_source, journal_tid, dispatch_result,
+ on_dispatched);
+ }
+
+ MOCK_METHOD7(execute_list_snaps, bool(uint64_t, const Extents&,
+ const SnapIds&, int, SnapshotDelta*,
+ DispatchResult*, Context*));
+ bool list_snaps(
+ uint64_t object_no, io::Extents&& extents, SnapIds&& snap_ids,
+ int list_snaps_flags, const ZTracer::Trace &parent_trace,
+ SnapshotDelta* snapshot_delta, int* object_dispatch_flags,
+ DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return execute_list_snaps(object_no, extents, snap_ids, list_snaps_flags,
+ snapshot_delta, 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));
+ MOCK_METHOD2(prepare_copyup, int(uint64_t, SnapshotSparseBufferlist*));
+};
+
+} // 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 000000000..5e700397b
--- /dev/null
+++ b/src/test/librbd/mock/io/MockObjectDispatcher.h
@@ -0,0 +1,44 @@
+// -*- 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_dispatch, void(ObjectDispatchInterface*));
+ MOCK_METHOD1(exists, bool(ObjectDispatchLayer));
+ MOCK_METHOD2(shut_down_dispatch, void(ObjectDispatchLayer, Context*));
+
+ MOCK_METHOD2(flush, void(FlushSource, Context*));
+
+ MOCK_METHOD1(invalidate_cache, void(Context*));
+ MOCK_METHOD1(reset_existence_cache, void(Context*));
+
+ MOCK_METHOD5(extent_overwritten, void(uint64_t, uint64_t, uint64_t, uint64_t,
+ uint64_t));
+
+ MOCK_METHOD2(prepare_copyup, int(uint64_t, SnapshotSparseBufferlist*));
+
+ MOCK_METHOD1(send, void(ObjectDispatchSpec*));
+};
+
+} // namespace io
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_IO_OBJECT_DISPATCHER_H
diff --git a/src/test/librbd/mock/io/MockQosImageDispatch.h b/src/test/librbd/mock/io/MockQosImageDispatch.h
new file mode 100644
index 000000000..e49897816
--- /dev/null
+++ b/src/test/librbd/mock/io/MockQosImageDispatch.h
@@ -0,0 +1,24 @@
+// -*- 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_QOS_IMAGE_DISPATCH_H
+#define CEPH_TEST_LIBRBD_MOCK_IO_QOS_IMAGE_DISPATCH_H
+
+#include "gmock/gmock.h"
+#include "librbd/io/Types.h"
+#include <atomic>
+
+struct Context;
+
+namespace librbd {
+namespace io {
+
+struct MockQosImageDispatch {
+ MOCK_METHOD4(needs_throttle, bool(bool, const Extents&,
+ std::atomic<uint32_t>*, Context*));
+};
+
+} // namespace io
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_IO_QOS_IMAGE_DISPATCH_H
diff --git a/src/test/librbd/mock/migration/MockSnapshotInterface.h b/src/test/librbd/mock/migration/MockSnapshotInterface.h
new file mode 100644
index 000000000..abb6d1a08
--- /dev/null
+++ b/src/test/librbd/mock/migration/MockSnapshotInterface.h
@@ -0,0 +1,44 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_LIBRBD_MOCK_MIGRATION_MOCK_SNAPSHOT_INTERFACE_H
+#define CEPH_TEST_LIBRBD_MOCK_MIGRATION_MOCK_SNAPSHOT_INTERFACE_H
+
+#include "include/buffer.h"
+#include "gmock/gmock.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ReadResult.h"
+#include "librbd/io/Types.h"
+#include "librbd/migration/SnapshotInterface.h"
+
+namespace librbd {
+namespace migration {
+
+struct MockSnapshotInterface : public SnapshotInterface {
+ MOCK_METHOD2(open, void(SnapshotInterface*, Context*));
+ MOCK_METHOD1(close, void(Context*));
+
+ MOCK_CONST_METHOD0(get_snap_info, const SnapInfo&());
+
+ MOCK_METHOD3(read, void(io::AioCompletion*, const io::Extents&,
+ io::ReadResult&));
+ void read(io::AioCompletion* aio_comp, io::Extents&& image_extents,
+ io::ReadResult&& read_result, int op_flags, int read_flags,
+ const ZTracer::Trace &parent_trace) override {
+ read(aio_comp, image_extents, read_result);
+ }
+
+ MOCK_METHOD3(list_snap, void(const io::Extents&, io::SparseExtents*,
+ Context*));
+ void list_snap(io::Extents&& image_extents, int list_snaps_flags,
+ io::SparseExtents* sparse_extents,
+ const ZTracer::Trace &parent_trace,
+ Context* on_finish) override {
+ list_snap(image_extents, sparse_extents, on_finish);
+ }
+};
+
+} // namespace migration
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_MIGRATION_MOCK_SNAPSHOT_INTERFACE_H
diff --git a/src/test/librbd/mock/migration/MockStreamInterface.h b/src/test/librbd/mock/migration/MockStreamInterface.h
new file mode 100644
index 000000000..36df86638
--- /dev/null
+++ b/src/test/librbd/mock/migration/MockStreamInterface.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_MIGRATION_MOCK_STREAM_INTERFACE_H
+#define CEPH_TEST_LIBRBD_MOCK_MIGRATION_MOCK_STREAM_INTERFACE_H
+
+#include "include/buffer.h"
+#include "gmock/gmock.h"
+#include "librbd/migration/StreamInterface.h"
+
+namespace librbd {
+namespace migration {
+
+struct MockStreamInterface : public StreamInterface {
+ MOCK_METHOD1(open, void(Context*));
+ MOCK_METHOD1(close, void(Context*));
+
+ MOCK_METHOD2(get_size, void(uint64_t*, Context*));
+
+ MOCK_METHOD3(read, void(const io::Extents&, bufferlist*, Context*));
+ void read(io::Extents&& byte_extents, bufferlist* bl, Context* on_finish) {
+ read(byte_extents, bl, on_finish);
+ }
+};
+
+} // namespace migration
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_MIGRATION_MOCK_STREAM_INTERFACE_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 000000000..92f30748c
--- /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_DiffRequest.cc b/src/test/librbd/object_map/test_mock_DiffRequest.cc
new file mode 100644
index 000000000..c25ae4a95
--- /dev/null
+++ b/src/test/librbd/object_map/test_mock_DiffRequest.cc
@@ -0,0 +1,493 @@
+// -*- 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 "include/rbd_types.h"
+#include "common/ceph_mutex.h"
+#include "librbd/object_map/DiffRequest.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+#include "librbd/object_map/DiffRequest.cc"
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InSequence;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+namespace librbd {
+namespace object_map {
+
+class TestMockObjectMapDiffRequest : public TestMockFixture {
+public:
+ typedef DiffRequest<MockTestImageCtx> MockDiffRequest;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+ }
+
+ void expect_get_flags(MockTestImageCtx& mock_image_ctx, uint64_t snap_id,
+ int32_t flags, int r) {
+ EXPECT_CALL(mock_image_ctx, get_flags(snap_id, _))
+ .WillOnce(WithArg<1>(Invoke([flags, r](uint64_t *out_flags) {
+ *out_flags = flags;
+ return r;
+ })));
+ }
+
+ template <typename Lambda>
+ void expect_load_map(MockTestImageCtx& mock_image_ctx, uint64_t snap_id,
+ const BitVector<2>& object_map, int r,
+ Lambda&& lambda) {
+ std::string snap_oid(ObjectMap<>::object_map_name(mock_image_ctx.id,
+ snap_id));
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(snap_oid, _, StrEq("rbd"), StrEq("object_map_load"), _,
+ _, _, _))
+ .WillOnce(WithArg<5>(Invoke([object_map, r, lambda=std::move(lambda)]
+ (bufferlist* out_bl) {
+ lambda();
+
+ auto out_object_map{object_map};
+ out_object_map.set_crc_enabled(false);
+ encode(out_object_map, *out_bl);
+ return r;
+ })));
+ }
+
+ void expect_load_map(MockTestImageCtx& mock_image_ctx, uint64_t snap_id,
+ const BitVector<2>& object_map, int r) {
+ expect_load_map(mock_image_ctx, snap_id, object_map, r, [](){});
+ }
+
+ librbd::ImageCtx* m_image_ctx = nullptr;
+ BitVector<2> m_object_diff_state;
+};
+
+TEST_F(TestMockObjectMapDiffRequest, InvalidStartSnap) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, CEPH_NOSNAP, 0,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, StartEndSnapEqual) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 1, 1,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_EQ(0U, m_object_diff_state.size());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, FastDiffDisabled) {
+ // negative test -- object-map implicitly enables fast-diff
+ REQUIRE(!is_feature_enabled(RBD_FEATURE_OBJECT_MAP));
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, FastDiffInvalid) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}}
+ };
+
+ InSequence seq;
+ expect_get_flags(mock_image_ctx, 1U, RBD_FLAG_FAST_DIFF_INVALID, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, FullDelta) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}},
+ {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ object_map_1.resize(object_count);
+ object_map_1[1] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+ expect_get_flags(mock_image_ctx, 2U, 0, 0);
+
+ BitVector<2> object_map_2;
+ object_map_2.resize(object_count);
+ object_map_2[1] = OBJECT_EXISTS_CLEAN;
+ object_map_2[2] = OBJECT_EXISTS;
+ object_map_2[3] = OBJECT_EXISTS;
+ expect_load_map(mock_image_ctx, 2U, object_map_2, 0);
+
+ expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+
+ BitVector<2> object_map_head;
+ object_map_head.resize(object_count);
+ object_map_head[1] = OBJECT_EXISTS_CLEAN;
+ object_map_head[2] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ BitVector<2> expected_diff_state;
+ expected_diff_state.resize(object_count);
+ expected_diff_state[1] = DIFF_STATE_DATA_UPDATED;
+ expected_diff_state[2] = DIFF_STATE_DATA_UPDATED;
+ expected_diff_state[3] = DIFF_STATE_HOLE_UPDATED;
+ ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_F(TestMockObjectMapDiffRequest, IntermediateDelta) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}},
+ {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ object_map_1.resize(object_count);
+ object_map_1[1] = OBJECT_EXISTS;
+ object_map_1[2] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+ expect_get_flags(mock_image_ctx, 2U, 0, 0);
+
+ BitVector<2> object_map_2;
+ object_map_2.resize(object_count);
+ object_map_2[1] = OBJECT_EXISTS_CLEAN;
+ object_map_2[2] = OBJECT_EXISTS;
+ object_map_2[3] = OBJECT_EXISTS;
+ expect_load_map(mock_image_ctx, 2U, object_map_2, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 1, 2,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ BitVector<2> expected_diff_state;
+ expected_diff_state.resize(object_count);
+ expected_diff_state[1] = DIFF_STATE_DATA;
+ expected_diff_state[2] = DIFF_STATE_DATA_UPDATED;
+ expected_diff_state[3] = DIFF_STATE_DATA_UPDATED;
+ ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_F(TestMockObjectMapDiffRequest, EndDelta) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}},
+ {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 2U, 0, 0);
+
+ BitVector<2> object_map_2;
+ object_map_2.resize(object_count);
+ object_map_2[1] = OBJECT_EXISTS_CLEAN;
+ object_map_2[2] = OBJECT_EXISTS;
+ object_map_2[3] = OBJECT_EXISTS;
+ expect_load_map(mock_image_ctx, 2U, object_map_2, 0);
+
+ expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+
+ BitVector<2> object_map_head;
+ object_map_head.resize(object_count);
+ object_map_head[1] = OBJECT_EXISTS_CLEAN;
+ object_map_head[2] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 2, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ BitVector<2> expected_diff_state;
+ expected_diff_state.resize(object_count);
+ expected_diff_state[1] = DIFF_STATE_DATA;
+ expected_diff_state[2] = DIFF_STATE_DATA;
+ expected_diff_state[3] = DIFF_STATE_HOLE_UPDATED;
+ ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_F(TestMockObjectMapDiffRequest, StartSnapDNE) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 1, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, EndSnapDNE) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ object_map_1.resize(object_count);
+ expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 1, 2,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, IntermediateSnapDNE) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}},
+ {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ object_map_1.resize(object_count);
+ object_map_1[1] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, 0,
+ [&mock_image_ctx]() { mock_image_ctx.snap_info.erase(2); });
+
+ expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+
+ BitVector<2> object_map_head;
+ object_map_head.resize(object_count);
+ object_map_head[1] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ BitVector<2> expected_diff_state;
+ expected_diff_state.resize(object_count);
+ expected_diff_state[1] = DIFF_STATE_DATA_UPDATED;
+ ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_F(TestMockObjectMapDiffRequest, LoadObjectMapDNE) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+
+ BitVector<2> object_map_head;
+ expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, -ENOENT);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, LoadIntermediateObjectMapDNE) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, -ENOENT);
+
+ expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+
+ BitVector<2> object_map_head;
+ object_map_head.resize(object_count);
+ object_map_head[1] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ BitVector<2> expected_diff_state;
+ expected_diff_state.resize(object_count);
+ expected_diff_state[1] = DIFF_STATE_DATA_UPDATED;
+ ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_F(TestMockObjectMapDiffRequest, LoadObjectMapError) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, -EPERM);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, ObjectMapTooSmall) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace object_map
+} // 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 000000000..5ea40c03d
--- /dev/null
+++ b/src/test/librbd/object_map/test_mock_InvalidateRequest.cc
@@ -0,0 +1,158 @@
+// -*- 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, true, &cond_ctx);
+
+ EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx),
+ exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _,
+ _, _))
+ .WillOnce(DoDefault());
+
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_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());
+
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_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());
+
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_lock};
+ request->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+}
+
+TEST_F(TestMockObjectMapInvalidateRequest, ErrorOnDiskUpdateWithoutLock) {
+ 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);
+
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_lock};
+ request->send();
+ }
+ ASSERT_EQ(-EROFS, cond_ctx.wait());
+
+ expect_unlock_exclusive_lock(*ictx);
+}
+
+TEST_F(TestMockObjectMapInvalidateRequest, ErrorOnDiskUpdateFailure) {
+ 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));
+
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_lock};
+ request->send();
+ }
+ ASSERT_EQ(-EINVAL, 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 000000000..a99cab61e
--- /dev/null
+++ b/src/test/librbd/object_map/test_mock_LockRequest.cc
@@ -0,0 +1,221 @@
+// -*- 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 = decltype(reply.lockers){
+ {rados::cls::lock::locker_id_t(entity1, "cookie1"),
+ rados::cls::lock::locker_info_t()},
+ {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 000000000..43d9c3b43
--- /dev/null
+++ b/src/test/librbd/object_map/test_mock_RefreshRequest.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/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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ MockLockRequest mock_lock_request;
+ MockRefreshRequest *req = new MockRefreshRequest(
+ mock_image_ctx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ MockRefreshRequest *req = new MockRefreshRequest(
+ mock_image_ctx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ MockRefreshRequest *req = new MockRefreshRequest(
+ mock_image_ctx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ MockRefreshRequest *req = new MockRefreshRequest(
+ mock_image_ctx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ MockRefreshRequest *req = new MockRefreshRequest(
+ mock_image_ctx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ MockRefreshRequest *req = new MockRefreshRequest(
+ mock_image_ctx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ MockRefreshRequest *req = new MockRefreshRequest(
+ mock_image_ctx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ MockRefreshRequest *req = new MockRefreshRequest(
+ mock_image_ctx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ MockRefreshRequest *req = new MockRefreshRequest(
+ mock_image_ctx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ MockRefreshRequest *req = new MockRefreshRequest(
+ mock_image_ctx, &object_map_lock, &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 000000000..d6a3dd91c
--- /dev/null
+++ b/src/test/librbd/object_map/test_mock_ResizeRequest.cc
@@ -0,0 +1,154 @@
+// -*- 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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ object_map.resize(1);
+
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *req = new ResizeRequest(
+ *ictx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ object_map.resize(1);
+
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *req = new ResizeRequest(
+ *ictx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ object_map.resize(1);
+
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *req = new ResizeRequest(
+ *ictx, &object_map_lock, &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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ object_map.resize(1);
+
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *req = new ResizeRequest(
+ *ictx, &object_map_lock, &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 000000000..7f77aaf83
--- /dev/null
+++ b/src/test/librbd/object_map/test_mock_SnapshotCreateRequest.cc
@@ -0,0 +1,232 @@
+// -*- 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) {
+ std::unique_lock image_locker{ictx->image_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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ 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_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ 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_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_lock};
+ request->send();
+ }
+ ASSERT_EQ(-ENOENT, 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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ 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_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_lock};
+ request->send();
+ }
+ ASSERT_EQ(-ENOENT, 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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ 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_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_lock};
+ request->send();
+ }
+ ASSERT_EQ(-ENOENT, 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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ 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_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_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 000000000..20318743d
--- /dev/null
+++ b/src/test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc
@@ -0,0 +1,345 @@
+// -*- 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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *request = new SnapshotRemoveRequest(
+ *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *request = new SnapshotRemoveRequest(
+ *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_lock};
+ request->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+
+ {
+ // shouldn't invalidate the HEAD revision when we fail to load
+ // the already deleted snapshot
+ std::shared_lock image_locker{ictx->image_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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *request = new SnapshotRemoveRequest(
+ *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *request = new SnapshotRemoveRequest(
+ *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *request = new SnapshotRemoveRequest(
+ *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *request = new SnapshotRemoveRequest(
+ *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *request = new SnapshotRemoveRequest(
+ *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx);
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ 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 = new librbd::ObjectMap<>(*ictx, ictx->snap_id);
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_lock};
+ om->set_object_map(object_map);
+ om->aio_save(&cond_ctx1);
+ om->put();
+ }
+ 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_lock, &object_map, snap_id, &cond_ctx2);
+ {
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::unique_lock image_locker{ictx->image_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 000000000..7b89a0996
--- /dev/null
+++ b/src/test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc
@@ -0,0 +1,148 @@
+// -*- 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());
+
+ {
+ std::shared_lock image_locker{ictx->image_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());
+
+ {
+ std::shared_lock image_locker{ictx->image_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 000000000..f91ee001d
--- /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 000000000..c240dec00
--- /dev/null
+++ b/src/test/librbd/object_map/test_mock_UpdateRequest.cc
@@ -0,0 +1,291 @@
+// -*- 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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ 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_lock, &object_map, CEPH_NOSNAP, 0, object_map.size(),
+ OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, false, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_lock};
+ std::unique_lock object_map_locker{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::shared_mutex object_map_lock =
+ ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ object_map.resize(1);
+
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *req = new UpdateRequest<>(
+ *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, 0, object_map.size(),
+ OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, false, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_lock};
+ std::unique_lock object_map_locker{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::shared_mutex object_map_lock =
+ ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ object_map.resize(1);
+
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *req = new UpdateRequest<>(
+ *ictx, &object_map_lock, &object_map, snap_id, 0, object_map.size(),
+ OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, false, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_lock};
+ std::unique_lock object_map_locker{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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ object_map.resize(1);
+
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *req = new UpdateRequest<>(
+ *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, 0, object_map.size(),
+ OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, false, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_lock};
+ std::unique_lock object_map_locker{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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ object_map.resize(1);
+
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *req = new UpdateRequest<>(
+ *ictx, &object_map_lock, &object_map, snap_id, 0, object_map.size(),
+ OBJECT_EXISTS_CLEAN, boost::optional<uint8_t>(), {}, false, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_lock};
+ std::unique_lock object_map_locker{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));
+
+ expect_unlock_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);
+
+ ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ object_map.resize(712312);
+
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *req = new UpdateRequest<>(
+ *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, 0, object_map.size(),
+ OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, false, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_lock};
+ std::unique_lock object_map_locker{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::shared_mutex object_map_lock = ceph::make_shared_mutex("lock");
+ ceph::BitVector<2> object_map;
+ object_map.resize(1);
+
+ C_SaferCond cond_ctx;
+ AsyncRequest<> *req = new UpdateRequest<>(
+ *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, 0, object_map.size(),
+ OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, true, &cond_ctx);
+ {
+ std::shared_lock image_locker{ictx->image_lock};
+ std::unique_lock object_map_locker{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 000000000..171ac41a7
--- /dev/null
+++ b/src/test/librbd/operation/test_mock_DisableFeaturesRequest.cc
@@ -0,0 +1,538 @@
+// -*- 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/Journal.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/journal/TypeTraits.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
+
+template<>
+struct Journal<MockOperationImageCtx> {
+ static void get_work_queue(CephContext*, MockContextWQ**) {
+ }
+};
+
+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) {
+ }
+};
+
+template <>
+struct TypeTraits<MockOperationImageCtx> {
+ typedef librbd::MockContextWQ ContextWQ;
+};
+
+} // 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 MirrorModeEnabler {
+ public:
+ MirrorModeEnabler(librados::IoCtx &ioctx, cls::rbd::MirrorMode mirror_mode)
+ : m_ioctx(ioctx), m_mirror_mode(mirror_mode) {
+ EXPECT_EQ(0, librbd::cls_client::mirror_uuid_set(&m_ioctx, "test-uuid"));
+ EXPECT_EQ(0, librbd::cls_client::mirror_mode_set(&m_ioctx, m_mirror_mode));
+ }
+
+ ~MirrorModeEnabler() {
+ EXPECT_EQ(0, librbd::cls_client::mirror_mode_set(
+ &m_ioctx, cls::rbd::MIRROR_MODE_DISABLED));
+ }
+ private:
+ librados::IoCtx &m_ioctx;
+ cls::rbd::MirrorMode m_mirror_mode;
+ };
+
+ 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_image_dispatcher, 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_image_dispatcher, 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;
+ 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationDisableFeaturesRequest, Mirroring) {
+ REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+ MirrorModeEnabler mirror_mode_enabler(m_ioctx, cls::rbd::MIRROR_MODE_POOL);
+
+ 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);
+
+ 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);
+ {
+ std::shared_lock 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;
+ 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);
+ {
+ std::shared_lock 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 000000000..b7bf7d178
--- /dev/null
+++ b/src/test/librbd/operation/test_mock_EnableFeaturesRequest.cc
@@ -0,0 +1,644 @@
+// -*- 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/Journal.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/journal/TypeTraits.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
+
+template<>
+struct Journal<MockOperationImageCtx> {
+ static void get_work_queue(CephContext*, MockContextWQ**) {
+ }
+};
+
+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;
+
+template <>
+struct TypeTraits<MockOperationImageCtx> {
+ typedef librbd::MockContextWQ ContextWQ;
+};
+
+} // namespace journal
+
+namespace mirror {
+
+template<>
+class EnableRequest<MockOperationImageCtx> {
+public:
+ static EnableRequest *s_instance;
+ Context *on_finish = nullptr;
+
+ static EnableRequest *create(MockOperationImageCtx *image_ctx,
+ cls::rbd::MirrorImageMode mirror_image_mode,
+ const std::string& non_primary_global_image_id,
+ bool image_clean, 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 MirrorModeEnabler {
+ public:
+ MirrorModeEnabler(librados::IoCtx &ioctx, cls::rbd::MirrorMode mirror_mode)
+ : m_ioctx(ioctx), m_mirror_mode(mirror_mode) {
+ EXPECT_EQ(0, librbd::cls_client::mirror_uuid_set(&m_ioctx, "test-uuid"));
+ EXPECT_EQ(0, librbd::cls_client::mirror_mode_set(&m_ioctx, m_mirror_mode));
+ }
+
+ ~MirrorModeEnabler() {
+ EXPECT_EQ(0, librbd::cls_client::mirror_mode_set(
+ &m_ioctx, cls::rbd::MIRROR_MODE_DISABLED));
+ }
+ private:
+ librados::IoCtx &m_ioctx;
+ cls::rbd::MirrorMode m_mirror_mode;
+ };
+
+ 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_image_dispatcher, 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_image_dispatcher, 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationEnableFeaturesRequest, Mirroring) {
+ REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+ MirrorModeEnabler mirror_mode_enabler(m_ioctx, cls::rbd::MIRROR_MODE_POOL);
+
+ 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);
+
+ 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);
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ req->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationEnableFeaturesRequest, JournalingError) {
+ REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+ MirrorModeEnabler mirror_mode_enabler(m_ioctx, cls::rbd::MIRROR_MODE_POOL);
+
+ 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);
+
+ 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);
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationEnableFeaturesRequest, MirroringError) {
+ REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+ MirrorModeEnabler mirror_mode_enabler(m_ioctx, cls::rbd::MIRROR_MODE_POOL);
+
+ 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);
+
+ 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);
+ {
+ std::shared_lock 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 000000000..5c5e7a375
--- /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 LambdaContext([&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);
+
+ {
+ std::shared_lock 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);
+
+ {
+ std::shared_lock 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 000000000..552ba5c97
--- /dev/null
+++ b/src/test/librbd/operation/test_mock_ResizeRequest.cc
@@ -0,0 +1,435 @@
+// -*- 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/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 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;
+
+ void expect_block_writes(MockImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, 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_image_dispatcher, 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, int r) {
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, send(_))
+ .WillOnce(Invoke([&mock_image_ctx, r](io::ImageDispatchSpec* spec) {
+ ASSERT_TRUE(boost::get<io::ImageDispatchSpec::Flush>(
+ &spec->request) != nullptr);
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto aio_comp = spec->aio_comp;
+ auto ctx = new LambdaContext([aio_comp](int r) {
+ if (r < 0) {
+ aio_comp->fail(r);
+ } else {
+ aio_comp->set_request_count(1);
+ aio_comp->add_request();
+ aio_comp->complete_request(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_image_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);
+ {
+ std::shared_lock 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;
+ expect_flush_cache(mock_image_ctx, 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;
+ expect_flush_cache(mock_image_ctx, 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;
+ expect_flush_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, 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;
+ expect_flush_cache(mock_image_ctx, 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;
+ expect_flush_cache(mock_image_ctx, 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 000000000..218fc6b04
--- /dev/null
+++ b/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc
@@ -0,0 +1,495 @@
+// -*- 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/mirror/snapshot/SetImageStateRequest.h"
+#include "librbd/operation/SnapshotCreateRequest.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+template<>
+class SetImageStateRequest<MockImageCtx> {
+public:
+ static SetImageStateRequest *s_instance;
+ Context *on_finish = nullptr;
+
+ static SetImageStateRequest *create(MockImageCtx *image_ctx, uint64_t snap_id,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ SetImageStateRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+SetImageStateRequest<MockImageCtx> *SetImageStateRequest<MockImageCtx>::s_instance;
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+// 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;
+ typedef mirror::snapshot::SetImageStateRequest<MockImageCtx> MockSetImageStateRequest;
+
+ void expect_notify_quiesce(MockImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.image_watcher, notify_quiesce(_, _, _))
+ .WillOnce(WithArg<2>(CompleteContext(
+ r, mock_image_ctx.image_ctx->op_work_queue)));
+ }
+
+ void expect_block_writes(MockImageCtx &mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, 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_set_image_state(
+ MockImageCtx &mock_image_ctx,
+ MockSetImageStateRequest &mock_set_image_state_request, int r) {
+ EXPECT_CALL(mock_set_image_state_request, send())
+ .WillOnce(FinishRequest(&mock_set_image_state_request, r,
+ &mock_image_ctx));
+ }
+
+ 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_image_dispatcher, unblock_writes())
+ .Times(1);
+ }
+
+ void expect_notify_unquiesce(MockImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.image_watcher, notify_unquiesce(_, _))
+ .WillOnce(WithArg<1>(
+ CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)));
+ }
+};
+
+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_notify_quiesce(mock_image_ctx, -EINVAL);
+ expect_block_writes(mock_image_ctx);
+ expect_allocate_snap_id(mock_image_ctx, 0);
+ expect_snap_create(mock_image_ctx, 0);
+ expect_object_map_snap_create(mock_image_ctx);
+ expect_update_snap_context(mock_image_ctx);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+ expect_unblock_writes(mock_image_ctx);
+ expect_notify_unquiesce(mock_image_ctx, -EINVAL);
+
+ C_SaferCond cond_ctx;
+ librbd::NoOpProgressContext prog_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(),
+ "snap1", 0, SNAP_CREATE_FLAG_IGNORE_NOTIFY_QUIESCE_ERROR, prog_ctx);
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ req->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotCreateRequest, NotifyQuiesceError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+
+ expect_op_work_queue(mock_image_ctx);
+
+ ::testing::InSequence seq;
+ expect_notify_quiesce(mock_image_ctx, -EINVAL);
+ expect_notify_unquiesce(mock_image_ctx, 0);
+
+ C_SaferCond cond_ctx;
+ librbd::NoOpProgressContext prog_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(),
+ "snap1", 0, 0, prog_ctx);
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ req->send();
+ }
+ ASSERT_EQ(-EINVAL, 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_notify_quiesce(mock_image_ctx, 0);
+ expect_block_writes(mock_image_ctx);
+ expect_allocate_snap_id(mock_image_ctx, -EINVAL);
+ expect_unblock_writes(mock_image_ctx);
+ expect_notify_unquiesce(mock_image_ctx, 0);
+
+ C_SaferCond cond_ctx;
+ librbd::NoOpProgressContext prog_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(),
+ "snap1", 0, 0, prog_ctx);
+ {
+ std::shared_lock 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_notify_quiesce(mock_image_ctx, 0);
+ expect_block_writes(mock_image_ctx);
+ expect_allocate_snap_id(mock_image_ctx, -ESTALE);
+ expect_snap_create(mock_image_ctx, -ESTALE);
+ expect_object_map_snap_create(mock_image_ctx);
+ expect_update_snap_context(mock_image_ctx);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+ expect_unblock_writes(mock_image_ctx);
+ expect_notify_unquiesce(mock_image_ctx, 0);
+
+ C_SaferCond cond_ctx;
+ librbd::NoOpProgressContext prog_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(),
+ "snap1", 0, 0, prog_ctx);
+ {
+ std::shared_lock 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_notify_quiesce(mock_image_ctx, 0);
+ 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);
+ expect_notify_unquiesce(mock_image_ctx, 0);
+
+ C_SaferCond cond_ctx;
+ librbd::NoOpProgressContext prog_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(),
+ "snap1", 0, 0, prog_ctx);
+ {
+ std::shared_lock 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_notify_quiesce(mock_image_ctx, 0);
+ 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);
+ expect_notify_unquiesce(mock_image_ctx, 0);
+
+ C_SaferCond cond_ctx;
+ librbd::NoOpProgressContext prog_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(),
+ "snap1", 0, 0, prog_ctx);
+ {
+ std::shared_lock 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_notify_quiesce(mock_image_ctx, 0);
+ 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_CALL(mock_image_ctx, rebuild_data_io_context());
+ expect_unblock_writes(mock_image_ctx);
+ expect_notify_unquiesce(mock_image_ctx, 0);
+
+ C_SaferCond cond_ctx;
+ librbd::NoOpProgressContext prog_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(),
+ "snap1", 0, SNAP_CREATE_FLAG_SKIP_OBJECT_MAP, prog_ctx);
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ req->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotCreateRequest, SkipNotifyQuiesce) {
+ 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);
+ expect_object_map_snap_create(mock_image_ctx);
+ expect_update_snap_context(mock_image_ctx);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+ expect_unblock_writes(mock_image_ctx);
+
+ C_SaferCond cond_ctx;
+ librbd::NoOpProgressContext prog_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(),
+ "snap1", 0, SNAP_CREATE_FLAG_SKIP_NOTIFY_QUIESCE, prog_ctx);
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ req->send();
+ }
+ ASSERT_EQ(0, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotCreateRequest, SetImageState) {
+ 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_notify_quiesce(mock_image_ctx, 0);
+ expect_block_writes(mock_image_ctx);
+ expect_allocate_snap_id(mock_image_ctx, 0);
+ expect_snap_create(mock_image_ctx, 0);
+ expect_object_map_snap_create(mock_image_ctx);
+ MockSetImageStateRequest mock_set_image_state_request;
+ expect_set_image_state(mock_image_ctx, mock_set_image_state_request, 0);
+ expect_update_snap_context(mock_image_ctx);
+ EXPECT_CALL(mock_image_ctx, rebuild_data_io_context());
+ expect_unblock_writes(mock_image_ctx);
+ expect_notify_unquiesce(mock_image_ctx, 0);
+
+ C_SaferCond cond_ctx;
+ librbd::NoOpProgressContext prog_ctx;
+ MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest(
+ mock_image_ctx, &cond_ctx,
+ cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {}, "", CEPH_NOSNAP},
+ "snap1", 0, 0, prog_ctx);
+ {
+ std::shared_lock 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 000000000..aa8c1e78d
--- /dev/null
+++ b/src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc
@@ -0,0 +1,193 @@
+// -*- 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");
+ {
+ std::shared_lock 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");
+ {
+ std::shared_lock 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");
+ {
+ std::shared_lock 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");
+ {
+ std::shared_lock 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");
+ {
+ std::shared_lock 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 000000000..886ac768e
--- /dev/null
+++ b/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc
@@ -0,0 +1,924 @@
+// -*- 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/mirror/snapshot/RemoveImageStateRequest.h"
+#include "librbd/operation/SnapshotRemoveRequest.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+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 mirror {
+namespace snapshot {
+
+template<>
+class RemoveImageStateRequest<MockImageCtx> {
+public:
+ static RemoveImageStateRequest *s_instance;
+ Context *on_finish = nullptr;
+
+ static RemoveImageStateRequest *create(MockImageCtx *image_ctx,
+ uint64_t snap_id,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ RemoveImageStateRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+RemoveImageStateRequest<MockImageCtx> *RemoveImageStateRequest<MockImageCtx>::s_instance;
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+// template definitions
+#include "librbd/operation/SnapshotRemoveRequest.cc"
+
+namespace librbd {
+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;
+ typedef mirror::snapshot::RemoveImageStateRequest<MockImageCtx> MockRemoveImageStateRequest;
+
+ 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_remove_image_state(
+ MockImageCtx &mock_image_ctx,
+ MockRemoveImageStateRequest &mock_remove_image_state_request, int r) {
+ EXPECT_CALL(mock_remove_image_state_request, send())
+ .WillOnce(FinishRequest(&mock_remove_image_state_request, r,
+ &mock_image_ctx));
+ }
+
+ 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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));
+ NoOpProgressContext prog_ctx;
+ ASSERT_EQ(0, ictx->operations->snap_create(
+ {cls::rbd::TrashSnapshotNamespace{}}, "snap1", 0, 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;
+
+ 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);
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ req->send();
+ }
+ ASSERT_EQ(-EBUSY, cond_ctx.wait());
+}
+
+TEST_F(TestMockOperationSnapshotRemoveRequest, MirrorSnapshot) {
+ 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, 0);
+
+ uint64_t snap_id = ictx->snap_info.rbegin()->first;
+ expect_snapshot_get(mock_image_ctx,
+ {snap_id, {cls::rbd::MirrorSnapshotNamespace{}},
+ "mirror", 123, {}, 0}, 0);
+
+ expect_get_parent_spec(mock_image_ctx, 0);
+ expect_object_map_snap_remove(mock_image_ctx, 0);
+ MockRemoveImageStateRequest mock_remove_image_state_request;
+ expect_remove_image_state(mock_image_ctx, mock_remove_image_state_request, 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::MirrorSnapshotNamespace(),
+ "mirror", snap_id);
+ {
+ std::shared_lock owner_locker{mock_image_ctx.owner_lock};
+ req->send();
+ }
+ ASSERT_EQ(0, 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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 000000000..65eac7a6d
--- /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_image_dispatcher, 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_image_dispatcher, 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_image_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);
+ {
+ std::shared_lock 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;
+ MockObjectMap mock_snap_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, 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;
+ MockObjectMap mock_snap_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, 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;
+ 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;
+ MockObjectMap mock_snap_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, 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 000000000..26b1be206
--- /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");
+ {
+ std::shared_lock 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");
+ {
+ std::shared_lock 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");
+ {
+ std::shared_lock 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");
+ {
+ std::shared_lock 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");
+ {
+ std::shared_lock 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");
+ {
+ std::shared_lock 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 000000000..cd18f1306
--- /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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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 000000000..a4b36d3cb
--- /dev/null
+++ b/src/test/librbd/rbdrw.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+"""
+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 blocklist 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 000000000..e41e58825
--- /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 000000000..1a512aa29
--- /dev/null
+++ b/src/test/librbd/test_DeepCopy.cc
@@ -0,0 +1,759 @@
+// -*- 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/Io.h"
+#include "librbd/api/Image.h"
+#include "librbd/api/Snapshot.h"
+#include "librbd/internal.h"
+#include "librbd/io/ReadResult.h"
+
+void register_test_deep_copy() {
+}
+
+namespace librbd {
+
+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, api::Io<>::flush(*m_src_ictx));
+ 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::api::Snapshot<>::list(m_src_ictx, src_snaps));
+ EXPECT_EQ(0, librbd::api::Snapshot<>::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;
+ {
+ std::shared_lock src_locker{m_src_ictx->image_lock};
+ std::shared_lock dst_locker{m_dst_ictx->image_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;
+ std::shared_lock dst_locker{m_dst_ictx->image_lock};
+ EXPECT_EQ(0, m_dst_ictx->test_flags(m_dst_ictx->snap_id,
+ RBD_FLAG_OBJECT_MAP_INVALID,
+ m_dst_ictx->image_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, api::Io<>::read(
+ *m_src_ictx, 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, api::Io<>::read(
+ *m_dst_ictx, 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()),
+ api::Io<>::write(*m_src_ictx, 0 * bl.length(), bl.length(),
+ bufferlist{bl}, 0));
+ ASSERT_EQ(static_cast<ssize_t>(bl.length()),
+ api::Io<>::write(*m_src_ictx, 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()),
+ api::Io<>::write(*m_src_ictx, 0 * bl.length(), bl.length(),
+ bufferlist{bl}, 0));
+ ASSERT_EQ(0, api::Io<>::flush(*m_src_ictx));
+
+ ASSERT_EQ(0, snap_create(*m_src_ictx, "snap1"));
+
+ ASSERT_EQ(static_cast<ssize_t>(bl.length()),
+ api::Io<>::write(*m_src_ictx, 1 * bl.length(), bl.length(),
+ bufferlist{bl}, 0));
+ bufferlist bl1;
+ bl1.append(std::string(1000, 'X'));
+ ASSERT_EQ(static_cast<ssize_t>(bl1.length()),
+ api::Io<>::write(*m_src_ictx, 0 * bl.length(), bl1.length(),
+ bufferlist{bl1}, 0));
+ ASSERT_EQ(static_cast<ssize_t>(bl1.length()),
+ api::Io<>::discard(*m_src_ictx, bl1.length() + 10,
+ bl1.length(), false));
+
+ ASSERT_EQ(0, api::Io<>::flush(*m_src_ictx));
+
+ ASSERT_EQ(0, snap_create(*m_src_ictx, "snap2"));
+ ASSERT_EQ(static_cast<ssize_t>(bl1.length()),
+ api::Io<>::write(*m_src_ictx, 1 * bl.length(), bl1.length(),
+ bufferlist{bl1}, 0));
+ ASSERT_EQ(static_cast<ssize_t>(bl1.length()),
+ api::Io<>::discard(*m_src_ictx, 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()),
+ api::Io<>::write(*m_src_ictx, 0, bl.length(), bufferlist{bl}, 0));
+ ASSERT_EQ(0, api::Io<>::flush(*m_src_ictx));
+
+ 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),
+ api::Io<>::discard(*m_src_ictx, 0, len, false));
+ }
+
+ void test_clone_discard() {
+ bufferlist bl;
+ bl.append(std::string(100, '1'));
+ ASSERT_EQ(static_cast<ssize_t>(bl.length()),
+ api::Io<>::write(*m_src_ictx, 0, bl.length(), bufferlist{bl}, 0));
+ ASSERT_EQ(0, api::Io<>::flush(*m_src_ictx));
+
+ 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),
+ api::Io<>::discard(*m_src_ictx, 0, len, false));
+ }
+
+ void test_clone_shrink() {
+ bufferlist bl;
+ bl.append(std::string(100, '1'));
+ ASSERT_EQ(static_cast<ssize_t>(bl.length()),
+ api::Io<>::write(*m_src_ictx, 0, bl.length(), bufferlist{bl}, 0));
+ ASSERT_EQ(0, api::Io<>::flush(*m_src_ictx));
+
+ 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()),
+ api::Io<>::write(*m_src_ictx, 0, bl.length(), bufferlist{bl}, 0));
+ ASSERT_EQ(0, api::Io<>::flush(*m_src_ictx));
+
+ 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()),
+ api::Io<>::write(*m_src_ictx, object_size, bl.length(),
+ bufferlist{bl}, 0));
+ ASSERT_EQ(0, api::Io<>::flush(*m_src_ictx));
+
+ 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()),
+ api::Io<>::discard(*m_src_ictx, object_size, bl.length(), false));
+ ASSERT_EQ(0, api::Io<>::flush(*m_src_ictx));
+
+ 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()),
+ api::Io<>::write(*m_src_ictx, 0 * bl.length(), bl.length(),
+ bufferlist{bl}, 0));
+ ASSERT_EQ(static_cast<ssize_t>(bl.length()),
+ api::Io<>::write(*m_src_ictx, 2 * bl.length(), bl.length(),
+ bufferlist{bl}, 0));
+ ASSERT_EQ(0, api::Io<>::flush(*m_src_ictx));
+
+ 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()),
+ api::Io<>::write(*m_src_ictx, 0 * bl.length(), bl1.length(),
+ bufferlist{bl1}, 0));
+ ASSERT_EQ(static_cast<ssize_t>(bl1.length()),
+ api::Io<>::discard(*m_src_ictx, bl1.length() + 10,
+ bl1.length(), false));
+ ASSERT_EQ(0, api::Io<>::flush(*m_src_ictx));
+
+ 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()),
+ api::Io<>::write(*m_src_ictx, 1 * bl.length(), bl1.length(),
+ bufferlist{bl1}, 0));
+ ASSERT_EQ(static_cast<ssize_t>(bl1.length()),
+ api::Io<>::discard(*m_src_ictx, 2 * bl1.length() + 10,
+ bl1.length(), false));
+ }
+
+ void test_stress() {
+ uint64_t initial_size, size;
+ {
+ std::shared_lock src_locker{m_src_ictx->image_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()),
+ api::Io<>::write(*m_src_ictx, 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),
+ api::Io<>::discard(*m_src_ictx, off, len, false));
+ }
+
+ ASSERT_EQ(0, api::Io<>::flush(*m_src_ictx));
+
+ 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));
+ {
+ std::shared_lock src_locker{m_src_ictx->image_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();
+}
+
+} // namespace librbd
diff --git a/src/test/librbd/test_Groups.cc b/src/test/librbd/test_Groups.cc
new file mode 100644
index 000000000..3182d2a20
--- /dev/null
+++ b/src/test/librbd/test_Groups.cc
@@ -0,0 +1,423 @@
+// -*- 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 <chrono>
+#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()));
+
+ struct Watcher {
+ static void quiesce_cb(void *arg) {
+ Watcher *watcher = static_cast<Watcher *>(arg);
+ watcher->handle_quiesce();
+ }
+ static void unquiesce_cb(void *arg) {
+ Watcher *watcher = static_cast<Watcher *>(arg);
+ watcher->handle_unquiesce();
+ }
+
+ rbd_image_t &image;
+ uint64_t handle = 0;
+ size_t quiesce_count = 0;
+ size_t unquiesce_count = 0;
+ int r = 0;
+
+ ceph::mutex lock = ceph::make_mutex("lock");
+ ceph::condition_variable cv;
+
+ Watcher(rbd_image_t &image) : image(image) {
+ }
+
+ void handle_quiesce() {
+ ASSERT_EQ(quiesce_count, unquiesce_count);
+ quiesce_count++;
+ rbd_quiesce_complete(image, handle, r);
+ }
+ void handle_unquiesce() {
+ std::unique_lock locker(lock);
+ unquiesce_count++;
+ cv.notify_one();
+ }
+ bool wait_for_unquiesce(size_t c) {
+ std::unique_lock locker(lock);
+ return cv.wait_for(locker, std::chrono::seconds(60),
+ [this, c]() { return unquiesce_count >= c; });
+ }
+ } watcher(image);
+
+ ASSERT_EQ(0, rbd_quiesce_watch(image, Watcher::quiesce_cb,
+ Watcher::unquiesce_cb, &watcher,
+ &watcher.handle));
+
+ ASSERT_EQ(0, rbd_group_snap_create(ioctx, group_name, snap_name));
+ ASSERT_TRUE(watcher.wait_for_unquiesce(1U));
+ ASSERT_EQ(1U, watcher.quiesce_count);
+
+ 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(-EINVAL, rbd_group_snap_create2(ioctx, group_name, snap_name,
+ RBD_SNAP_CREATE_SKIP_QUIESCE |
+ RBD_SNAP_CREATE_IGNORE_QUIESCE_ERROR));
+ watcher.r = -EINVAL;
+ ASSERT_EQ(-EINVAL, rbd_group_snap_create2(ioctx, group_name, snap_name, 0));
+
+ num_snaps = 1;
+ ASSERT_EQ(0, rbd_group_snap_list(ioctx, group_name, snaps,
+ sizeof(rbd_group_snap_info_t),
+ &num_snaps));
+
+ watcher.quiesce_count = 0;
+ watcher.unquiesce_count = 0;
+ ASSERT_EQ(0, rbd_group_snap_create2(ioctx, group_name, snap_name,
+ RBD_SNAP_CREATE_SKIP_QUIESCE));
+ ASSERT_EQ(0U, watcher.quiesce_count);
+ num_snaps = 1;
+ ASSERT_EQ(1, rbd_group_snap_list(ioctx, group_name, snaps,
+ sizeof(rbd_group_snap_info_t),
+ &num_snaps));
+ 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_create2(ioctx, group_name, snap_name,
+ RBD_SNAP_CREATE_IGNORE_QUIESCE_ERROR));
+ ASSERT_EQ(1, rbd_group_snap_list(ioctx, group_name, snaps,
+ sizeof(rbd_group_snap_info_t),
+ &num_snaps));
+ 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_quiesce_unwatch(image, watcher.handle));
+ 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_snap_create(ioctx, group_name, snap_name));
+ ASSERT_EQ(0, rbd.group_snap_list(ioctx, group_name, &snaps,
+ sizeof(librbd::group_snap_info_t)));
+ ASSERT_EQ(1U, snaps.size());
+ ASSERT_EQ(0, rbd.group_snap_remove(ioctx, group_name, snap_name));
+
+ ASSERT_EQ(-EINVAL, rbd.group_snap_create2(ioctx, group_name, snap_name,
+ RBD_SNAP_CREATE_SKIP_QUIESCE |
+ RBD_SNAP_CREATE_IGNORE_QUIESCE_ERROR));
+ 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_snap_create2(ioctx, group_name, snap_name,
+ RBD_SNAP_CREATE_SKIP_QUIESCE));
+ snaps.clear();
+ ASSERT_EQ(0, rbd.group_snap_list(ioctx, group_name, &snaps,
+ sizeof(librbd::group_snap_info_t)));
+ ASSERT_EQ(1U, snaps.size());
+
+ ASSERT_EQ(0, rbd.group_snap_remove(ioctx, group_name, snap_name));
+ 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 000000000..f02c7b37b
--- /dev/null
+++ b/src/test/librbd/test_ImageWatcher.cc
@@ -0,0 +1,885 @@
+// -*- 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/ceph_mutex.h"
+#include "common/errno.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 "test/librados/test.h"
+#include "gtest/gtest.h"
+#include <boost/assign/std/set.hpp>
+#include <boost/assign/std/map.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)
+ {
+ }
+
+ 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;
+ */
+
+ std::lock_guard 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.notify_all();
+ }
+
+ 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) {
+ std::unique_lock l{m_callback_lock};
+ while (m_notifies.size() < m_notify_acks.size()) {
+ if (m_callback_cond.wait_for(l, 10s) == std::cv_status::timeout) {
+ 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_SNAP_CREATE:
+ {
+ SnapCreatePayload payload;
+ payload.decode(7, iter);
+ *id = payload.async_request_id;
+ }
+ return true;
+ case NOTIFY_OP_SNAP_RENAME:
+ {
+ SnapRenamePayload payload;
+ payload.decode(7, iter);
+ *id = payload.async_request_id;
+ }
+ return true;
+ case NOTIFY_OP_SNAP_REMOVE:
+ {
+ SnapRemovePayload payload;
+ payload.decode(7, iter);
+ *id = payload.async_request_id;
+ }
+ return true;
+ case NOTIFY_OP_SNAP_PROTECT:
+ {
+ SnapProtectPayload payload;
+ payload.decode(7, iter);
+ *id = payload.async_request_id;
+ }
+ return true;
+ case NOTIFY_OP_SNAP_UNPROTECT:
+ {
+ SnapUnprotectPayload payload;
+ payload.decode(7, iter);
+ *id = payload.async_request_id;
+ }
+ return true;
+ case NOTIFY_OP_RENAME:
+ {
+ RenamePayload payload;
+ payload.decode(7, 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(new 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(new 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;
+
+ ceph::mutex m_callback_lock = ceph::make_mutex("m_callback_lock");
+ ceph::condition_variable m_callback_cond;
+
+};
+
+struct ProgressContext : public librbd::ProgressContext {
+ ceph::mutex mutex = ceph::make_mutex("ProgressContext::mutex");
+ ceph::condition_variable cond;
+ bool received;
+ uint64_t offset;
+ uint64_t total;
+
+ ProgressContext() : received(false),
+ offset(0), total(0) {}
+
+ int update_progress(uint64_t offset_, uint64_t total_) override {
+ std::lock_guard l{mutex};
+ offset = offset_;
+ total = total_;
+ received = true;
+ cond.notify_all();
+ return 0;
+ }
+
+ bool wait(librbd::ImageCtx *ictx, uint64_t offset_, uint64_t total_) {
+ std::unique_lock l{mutex};
+ while (!received) {
+ if (cond.wait_for(l, 10s) == std::cv_status::timeout) {
+ 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()() {
+ std::shared_lock 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()() {
+ std::shared_lock l{ictx->owner_lock};
+ C_SaferCond ctx;
+ ictx->image_watcher->notify_resize(0, 0, true, *progress_context, &ctx);
+ result = ctx.wait();
+ }
+};
+
+struct SnapCreateTask {
+ librbd::ImageCtx *ictx;
+ ProgressContext *progress_context;
+ int result;
+
+ SnapCreateTask(librbd::ImageCtx *ictx_, ProgressContext *ctx)
+ : ictx(ictx_), progress_context(ctx), result(0) {}
+
+ void operator()() {
+ std::shared_lock l{ictx->owner_lock};
+ C_SaferCond ctx;
+ ictx->image_watcher->notify_snap_create(0, cls::rbd::UserSnapshotNamespace(),
+ "snap", 0, *progress_context, &ctx);
+ result = ctx.wait();
+ }
+};
+
+struct SnapRenameTask {
+ librbd::ImageCtx *ictx;
+ int result = 0;
+
+ SnapRenameTask(librbd::ImageCtx *ictx)
+ : ictx(ictx) {
+ }
+
+ void operator()() {
+ std::shared_lock l{ictx->owner_lock};
+ C_SaferCond ctx;
+ ictx->image_watcher->notify_snap_rename(0, 1, "snap-rename", &ctx);
+ result = ctx.wait();
+ }
+};
+
+struct SnapRemoveTask {
+ librbd::ImageCtx *ictx;
+ int result = 0;
+
+ SnapRemoveTask(librbd::ImageCtx *ictx)
+ : ictx(ictx) {
+ }
+
+ void operator()() {
+ std::shared_lock l{ictx->owner_lock};
+ C_SaferCond ctx;
+ ictx->image_watcher->notify_snap_remove(
+ 0, cls::rbd::UserSnapshotNamespace(), "snap", &ctx);
+ result = ctx.wait();
+ }
+};
+
+struct SnapProtectTask {
+ librbd::ImageCtx *ictx;
+ int result = 0;
+
+ SnapProtectTask(librbd::ImageCtx *ictx)
+ : ictx(ictx) {
+ }
+
+ void operator()() {
+ std::shared_lock l{ictx->owner_lock};
+ C_SaferCond ctx;
+ ictx->image_watcher->notify_snap_protect(
+ 0, cls::rbd::UserSnapshotNamespace(), "snap", &ctx);
+ result = ctx.wait();
+ }
+};
+
+struct SnapUnprotectTask {
+ librbd::ImageCtx *ictx;
+ int result = 0;
+
+ SnapUnprotectTask(librbd::ImageCtx *ictx)
+ : ictx(ictx) {
+ }
+
+ void operator()() {
+ std::shared_lock l{ictx->owner_lock};
+ C_SaferCond ctx;
+ ictx->image_watcher->notify_snap_unprotect(
+ 0, cls::rbd::UserSnapshotNamespace(), "snap", &ctx);
+ result = ctx.wait();
+ }
+};
+
+struct RenameTask {
+ librbd::ImageCtx *ictx;
+ int result = 0;
+
+ RenameTask(librbd::ImageCtx *ictx)
+ : ictx(ictx) {
+ }
+
+ void operator()() {
+ std::shared_lock l{ictx->owner_lock};
+ C_SaferCond ctx;
+ ictx->image_watcher->notify_rename(0, "new_name", &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()() {
+ std::shared_lock 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, ClsLockType::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, ClsLockType::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, ClsLockType::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, ClsLockType::EXCLUSIVE,
+ "auto " + stringify(m_watch_ctx->get_handle())));
+
+ m_notify_acks = {{NOTIFY_OP_SNAP_CREATE, create_response_message(0)}};
+
+ ProgressContext progress_context;
+ SnapCreateTask snap_create_task(ictx, &progress_context);
+ boost::thread thread(boost::ref(snap_create_task));
+
+ ASSERT_TRUE(wait_for_notifies(*ictx));
+
+ NotifyOps expected_notify_ops;
+ expected_notify_ops += NOTIFY_OP_SNAP_CREATE;
+ ASSERT_EQ(expected_notify_ops, m_notifies);
+
+ AsyncRequestId async_request_id;
+ ASSERT_TRUE(extract_async_request_id(NOTIFY_OP_SNAP_CREATE,
+ &async_request_id));
+
+ ASSERT_EQ(0, notify_async_progress(ictx, async_request_id, 1, 10));
+ ASSERT_TRUE(progress_context.wait(ictx, 1, 10));
+
+ ASSERT_EQ(0, notify_async_complete(ictx, async_request_id, 0));
+
+ ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10)));
+ ASSERT_EQ(0, snap_create_task.result);
+}
+
+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, ClsLockType::EXCLUSIVE,
+ "auto " + stringify(m_watch_ctx->get_handle())));
+
+ m_notify_acks = {{NOTIFY_OP_SNAP_CREATE, create_response_message(-EEXIST)}};
+
+ std::shared_lock l{ictx->owner_lock};
+ C_SaferCond notify_ctx;
+ librbd::NoOpProgressContext prog_ctx;
+ ictx->image_watcher->notify_snap_create(0, cls::rbd::UserSnapshotNamespace(),
+ "snap", 0, prog_ctx, &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, ClsLockType::EXCLUSIVE,
+ "auto " + stringify(m_watch_ctx->get_handle())));
+
+ m_notify_acks = {{NOTIFY_OP_SNAP_RENAME, create_response_message(0)}};
+
+ SnapRenameTask snap_rename_task(ictx);
+ boost::thread thread(boost::ref(snap_rename_task));
+
+ ASSERT_TRUE(wait_for_notifies(*ictx));
+
+ NotifyOps expected_notify_ops;
+ expected_notify_ops += NOTIFY_OP_SNAP_RENAME;
+ ASSERT_EQ(expected_notify_ops, m_notifies);
+
+ AsyncRequestId async_request_id;
+ ASSERT_TRUE(extract_async_request_id(NOTIFY_OP_SNAP_RENAME,
+ &async_request_id));
+
+ ASSERT_EQ(0, notify_async_complete(ictx, async_request_id, 0));
+
+ ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10)));
+ ASSERT_EQ(0, snap_rename_task.result);
+}
+
+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, ClsLockType::EXCLUSIVE,
+ "auto " + stringify(m_watch_ctx->get_handle())));
+
+ m_notify_acks = {{NOTIFY_OP_SNAP_RENAME, create_response_message(-EEXIST)}};
+
+ std::shared_lock l{ictx->owner_lock};
+ C_SaferCond notify_ctx;
+ ictx->image_watcher->notify_snap_rename(0, 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, ClsLockType::EXCLUSIVE,
+ "auto " + stringify(m_watch_ctx->get_handle())));
+
+ m_notify_acks = {{NOTIFY_OP_SNAP_REMOVE, create_response_message(0)}};
+
+ SnapRemoveTask snap_remove_task(ictx);
+ boost::thread thread(boost::ref(snap_remove_task));
+
+ ASSERT_TRUE(wait_for_notifies(*ictx));
+
+ NotifyOps expected_notify_ops;
+ expected_notify_ops += NOTIFY_OP_SNAP_REMOVE;
+ ASSERT_EQ(expected_notify_ops, m_notifies);
+
+ AsyncRequestId async_request_id;
+ ASSERT_TRUE(extract_async_request_id(NOTIFY_OP_SNAP_REMOVE,
+ &async_request_id));
+
+ ASSERT_EQ(0, notify_async_complete(ictx, async_request_id, 0));
+
+ ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10)));
+ ASSERT_EQ(0, snap_remove_task.result);
+}
+
+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, ClsLockType::EXCLUSIVE,
+ "auto " + stringify(m_watch_ctx->get_handle())));
+
+ m_notify_acks = {{NOTIFY_OP_SNAP_PROTECT, create_response_message(0)}};
+
+ SnapProtectTask snap_protect_task(ictx);
+ boost::thread thread(boost::ref(snap_protect_task));
+
+ ASSERT_TRUE(wait_for_notifies(*ictx));
+
+ NotifyOps expected_notify_ops;
+ expected_notify_ops += NOTIFY_OP_SNAP_PROTECT;
+ ASSERT_EQ(expected_notify_ops, m_notifies);
+
+ AsyncRequestId async_request_id;
+ ASSERT_TRUE(extract_async_request_id(NOTIFY_OP_SNAP_PROTECT,
+ &async_request_id));
+
+ ASSERT_EQ(0, notify_async_complete(ictx, async_request_id, 0));
+
+ ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10)));
+ ASSERT_EQ(0, snap_protect_task.result);
+}
+
+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, ClsLockType::EXCLUSIVE,
+ "auto " + stringify(m_watch_ctx->get_handle())));
+
+ m_notify_acks = {{NOTIFY_OP_SNAP_UNPROTECT, create_response_message(0)}};
+
+ SnapUnprotectTask snap_unprotect_task(ictx);
+ boost::thread thread(boost::ref(snap_unprotect_task));
+
+ ASSERT_TRUE(wait_for_notifies(*ictx));
+
+ NotifyOps expected_notify_ops;
+ expected_notify_ops += NOTIFY_OP_SNAP_UNPROTECT;
+ ASSERT_EQ(expected_notify_ops, m_notifies);
+
+ AsyncRequestId async_request_id;
+ ASSERT_TRUE(extract_async_request_id(NOTIFY_OP_SNAP_UNPROTECT,
+ &async_request_id));
+
+ ASSERT_EQ(0, notify_async_complete(ictx, async_request_id, 0));
+
+ ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10)));
+ ASSERT_EQ(0, snap_unprotect_task.result);
+}
+
+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, ClsLockType::EXCLUSIVE,
+ "auto " + stringify(m_watch_ctx->get_handle())));
+
+ m_notify_acks = {{NOTIFY_OP_RENAME, create_response_message(0)}};
+
+ RenameTask rename_task(ictx);
+ boost::thread thread(boost::ref(rename_task));
+
+ ASSERT_TRUE(wait_for_notifies(*ictx));
+
+ NotifyOps expected_notify_ops;
+ expected_notify_ops += NOTIFY_OP_RENAME;
+ ASSERT_EQ(expected_notify_ops, m_notifies);
+
+ AsyncRequestId async_request_id;
+ ASSERT_TRUE(extract_async_request_id(NOTIFY_OP_RENAME,
+ &async_request_id));
+
+ ASSERT_EQ(0, notify_async_complete(ictx, async_request_id, 0));
+
+ ASSERT_TRUE(thread.timed_join(boost::posix_time::seconds(10)));
+ ASSERT_EQ(0, rename_task.result);
+}
+
+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, ClsLockType::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, ClsLockType::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, ClsLockType::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, ClsLockType::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 000000000..959eb0934
--- /dev/null
+++ b/src/test/librbd/test_Migration.cc
@@ -0,0 +1,1359 @@
+// -*- 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/Io.h"
+#include "librbd/api/Migration.h"
+#include "librbd/api/Mirror.h"
+#include "librbd/api/Namespace.h"
+#include "librbd/api/Snapshot.h"
+#include "librbd/image/AttachChildRequest.h"
+#include "librbd/image/AttachParentRequest.h"
+#include "librbd/internal.h"
+#include "librbd/io/ReadResult.h"
+#include "common/Cond.h"
+#include <boost/scope_exit.hpp>
+
+void register_test_migration() {
+}
+
+namespace librbd {
+
+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::api::Snapshot<>::list(m_ref_ictx, src_snaps));
+ EXPECT_EQ(0, librbd::api::Snapshot<>::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;
+ {
+ std::shared_lock src_locker{src_ictx->image_lock};
+ std::shared_lock dst_locker{dst_ictx->image_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;
+ std::shared_lock dst_locker{dst_ictx->image_lock};
+ EXPECT_EQ(0, dst_ictx->test_flags(dst_ictx->snap_id,
+ RBD_FLAG_OBJECT_MAP_INVALID,
+ dst_ictx->image_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, api::Io<>::read(
+ *src_ictx, 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, api::Io<>::read(
+ *dst_ictx, 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;
+ std::cout << "src block: " << src_ictx->id << ": " << std::endl; src_bl.hexdump(std::cout);
+ std::cout << "dst block: " << dst_ictx->id << ": " << 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),
+ api::Io<>::write(*m_ref_ictx, off, len, std::move(ref_bl), 0));
+ bufferlist bl;
+ bl.append(std::string(len, c));
+ ASSERT_EQ(static_cast<ssize_t>(len),
+ api::Io<>::write(*m_ictx, 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),
+ api::Io<>::discard(*m_ref_ictx, off, len, false));
+ ASSERT_EQ(static_cast<ssize_t>(len),
+ api::Io<>::discard(*m_ictx, off, len, false));
+ }
+
+ void flush() {
+ ASSERT_EQ(0, TestFixture::flush_writeback_cache(m_ref_ictx));
+ ASSERT_EQ(0, TestFixture::flush_writeback_cache(m_ictx));
+ }
+
+ 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, api::Io<>::read(
+ *child_ictx, 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;
+ features &= ~RBD_FEATURE_DIRTY_CACHE;
+ 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, RBD_MIRROR_IMAGE_MODE_JOURNAL, 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, RBD_MIRROR_IMAGE_MODE_JOURNAL, 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, RBD_MIRROR_IMAGE_MODE_JOURNAL, 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, RBD_MIRROR_IMAGE_MODE_JOURNAL, 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);
+}
+
+} // namespace librbd
diff --git a/src/test/librbd/test_MirroringWatcher.cc b/src/test/librbd/test_MirroringWatcher.cc
new file mode 100644
index 000000000..6038b5540
--- /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 000000000..61497b2af
--- /dev/null
+++ b/src/test/librbd/test_ObjectMap.cc
@@ -0,0 +1,238 @@
+// -*- 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 = new librbd::ObjectMap<>(*ictx, ictx->snap_id);
+ object_map->open(&ctx);
+ int r = ctx.wait();
+ object_map->put();
+
+ return r;
+ }
+};
+
+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;
+ {
+ std::unique_lock 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;
+ {
+ std::unique_lock 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;
+ {
+ std::unique_lock 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;
+ {
+ std::unique_lock 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 LambdaContext([&throttle, object_no](int r) {
+ ASSERT_EQ(0, r) << "object_no=" << object_no;
+ throttle.end_op(r);
+ });
+
+ std::shared_lock owner_locker{ictx->owner_lock};
+ std::shared_lock image_locker{ictx->image_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 000000000..43921df5b
--- /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 000000000..b47708279
--- /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 000000000..9ddebec48
--- /dev/null
+++ b/src/test/librbd/test_fixture.cc
@@ -0,0 +1,165 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#include "common/Cond.h"
+#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/io/ImageDispatchSpec.h"
+#include "librbd/Operations.h"
+#include "librbd/api/Io.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());
+ librados::Rados rados(m_ioctx);
+ rados.conf_set("rbd_persistent_cache_path", ".");
+
+ 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) {
+ librbd::NoOpProgressContext prog_ctx;
+ return ictx.operations->snap_create(cls::rbd::UserSnapshotNamespace(),
+ snap_name.c_str(), 0, prog_ctx);
+}
+
+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 = librbd::api::Io<>::write(ictx, 0, 0, {}, 0);
+ if (r != 0) {
+ return r;
+ }
+
+ std::shared_lock owner_locker{ictx.owner_lock};
+ ceph_assert(ictx.exclusive_lock != nullptr);
+ return ictx.exclusive_lock->is_lock_owner() ? 0 : -EINVAL;
+}
+
+int TestFixture::flush_writeback_cache(librbd::ImageCtx *image_ctx) {
+ if (image_ctx->test_features(RBD_FEATURE_DIRTY_CACHE)) {
+ // cache exists. Close to flush data
+ C_SaferCond ctx;
+ auto aio_comp = librbd::io::AioCompletion::create_and_start(
+ &ctx, image_ctx, librbd::io::AIO_TYPE_FLUSH);
+ auto req = librbd::io::ImageDispatchSpec::create_flush(
+ *image_ctx, librbd::io::IMAGE_DISPATCH_LAYER_INTERNAL_START, aio_comp,
+ librbd::io::FLUSH_SOURCE_INTERNAL, {});
+ req->send();
+ return ctx.wait();
+ } else {
+ return librbd::api::Io<>::flush(*image_ctx);
+ }
+}
diff --git a/src/test/librbd/test_fixture.h b/src/test/librbd/test_fixture.h
new file mode 100644
index 000000000..d3bd7b812
--- /dev/null
+++ b/src/test/librbd/test_fixture.h
@@ -0,0 +1,60 @@
+// -*- 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 flush_writeback_cache(librbd::ImageCtx *image_ctx);
+
+ 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 000000000..4a793a021
--- /dev/null
+++ b/src/test/librbd/test_internal.cc
@@ -0,0 +1,1789 @@
+// -*- 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/Io.h"
+#include "librbd/api/Migration.h"
+#include "librbd/api/PoolMetadata.h"
+#include "librbd/api/Snapshot.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ImageRequest.h"
+#include "osdc/Striper.h"
+#include "common/Cond.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() {
+}
+
+namespace librbd {
+
+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;
+ {
+ std::unique_lock 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, ClsLockType::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, ClsLockType::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, ClsLockType::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, ClsLockType::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, ClsLockType::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);
+ api::Io<>::aio_write(*ictx, c, 0, buffer.size(), std::move(bl), 0, true);
+
+ 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, ClsLockType::EXCLUSIVE, "manually locked"));
+
+ Context *ctx = new DummyContext();
+ auto c = librbd::io::AioCompletion::create(ctx);
+ c->get();
+ api::Io<>::aio_discard(*ictx, c, 0, 256, false, true);
+
+ 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;
+ {
+ std::unique_lock l{ictx->owner_lock};
+ ictx->exclusive_lock->try_acquire_lock(&ctx);
+ }
+
+ ASSERT_EQ(0, ctx.wait());
+ {
+ std::shared_lock 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);
+ {
+ std::shared_lock 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;
+ {
+ std::unique_lock l{ictx->owner_lock};
+ ictx->exclusive_lock->try_acquire_lock(&ctx);
+ }
+
+ std::shared_lock 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;
+ }
+
+ std::shared_lock 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);
+
+ uint8_t original_pairs_num = 0;
+ if (ictx->test_features(RBD_FEATURE_DIRTY_CACHE)) {
+ original_pairs_num = 1;
+ }
+ ASSERT_EQ(original_pairs_num + 5, 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(original_pairs_num + 3, 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));
+
+ bool sparse_read_supported = is_sparse_read_supported(
+ ictx->data_ctx, ictx->get_object_name(10));
+
+ bufferlist bl;
+ bl.append(std::string(256, '1'));
+ ASSERT_EQ(256, api::Io<>::write(*ictx, 0, bl.length(), bufferlist{bl}, 0));
+ ASSERT_EQ(256, api::Io<>::write(*ictx, 1024, 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, api::Io<>::write(*ictx2, 256, bl.length(), bufferlist{bl},
+ 0));
+
+ ASSERT_EQ(0, flush_writeback_cache(ictx2));
+ 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));
+
+ uint64_t copyup_end = ictx2->enable_sparse_copyup ? 1024 + 256 : 1 << order;
+ std::vector< std::pair<uint64_t,uint64_t> > expected_overlap =
+ boost::assign::list_of(
+ std::make_pair(0, 256))(
+ std::make_pair(512, copyup_end - 512));
+ 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,
+ api::Io<>::read(*ictx2, 0, 256,
+ librbd::io::ReadResult{read_result}, 0));
+ ASSERT_TRUE(bl.contents_equal(read_bl));
+
+ ASSERT_EQ(256,
+ api::Io<>::read(*ictx2, 1024, 256,
+ librbd::io::ReadResult{read_result}, 0));
+ ASSERT_TRUE(bl.contents_equal(read_bl));
+
+ ASSERT_EQ(256,
+ api::Io<>::read(*ictx2, 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 sparseness was preserved
+ {
+ librados::IoCtx io_ctx;
+ io_ctx.dup(m_ioctx);
+ librados::Rados rados(io_ctx);
+ EXPECT_EQ(0, rados.conf_set("rbd_cache", "false"));
+ EXPECT_EQ(0, rados.conf_set("rbd_sparse_read_threshold_bytes", "256"));
+ auto ictx3 = new librbd::ImageCtx(clone_name, "", snap_name, io_ctx,
+ true);
+ ASSERT_EQ(0, ictx3->state->open(0));
+ BOOST_SCOPE_EXIT(ictx3) {
+ ictx3->state->close();
+ } BOOST_SCOPE_EXIT_END;
+ std::vector<std::pair<uint64_t, uint64_t>> expected_m;
+ bufferlist expected_bl;
+ if (ictx3->enable_sparse_copyup && sparse_read_supported) {
+ if (snap_name == NULL) {
+ expected_m = {{0, 512}, {1024, 256}};
+ expected_bl.append(std::string(256 * 3, '1'));
+ } else {
+ expected_m = {{0, 256}, {1024, 256}};
+ expected_bl.append(std::string(256 * 2, '1'));
+ }
+ } else {
+ expected_m = {{0, 1024 + 256}};
+ if (snap_name == NULL) {
+ expected_bl.append(std::string(256 * 2, '1'));
+ expected_bl.append(std::string(256 * 2, '\0'));
+ expected_bl.append(std::string(256 * 1, '1'));
+ } else {
+ expected_bl.append(std::string(256 * 1, '1'));
+ expected_bl.append(std::string(256 * 3, '\0'));
+ expected_bl.append(std::string(256 * 1, '1'));
+ }
+ }
+ std::vector<std::pair<uint64_t, uint64_t>> read_m;
+ librbd::io::ReadResult sparse_read_result{&read_m, &read_bl};
+ EXPECT_EQ(1024 + 256,
+ api::Io<>::read(*ictx3, 0, 1024 + 256,
+ librbd::io::ReadResult{sparse_read_result}, 0));
+ EXPECT_EQ(expected_m, read_m);
+ EXPECT_TRUE(expected_bl.contents_equal(read_bl));
+ }
+
+ // 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 = new librbd::ObjectMap<>(*ictx2, ictx2->snap_id);
+ C_SaferCond ctx;
+ object_map->open(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ std::shared_lock image_locker{ictx2->image_lock};
+ ASSERT_EQ(state, (*object_map)[0]);
+ object_map->put();
+ }
+ }
+}
+
+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, api::Io<>::write(*ictx2, 256, bl.length(), bufferlist{bl}, 0));
+
+ ASSERT_EQ(0, flush_writeback_cache(ictx2));
+
+ 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,
+ api::Io<>::read(*ictx2, 0, 256,
+ librbd::io::ReadResult{read_result}, 0));
+ ASSERT_TRUE(read_bl.is_zero());
+
+ ASSERT_EQ(256,
+ api::Io<>::read(*ictx2, 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 = new librbd::ObjectMap<>(*ictx2, ictx2->snap_id);
+ C_SaferCond ctx;
+ object_map->open(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ std::shared_lock image_locker{ictx2->image_lock};
+ ASSERT_EQ(state, (*object_map)[0]);
+ object_map->put();
+ }
+ }
+}
+
+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, api::Io<>::write(*ictx2, 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,
+ api::Io<>::read(*ictx2, 0, 256,
+ librbd::io::ReadResult{read_result}, 0));
+ ASSERT_TRUE(read_bl.is_zero());
+
+ ASSERT_EQ(256,
+ api::Io<>::read(*ictx2, 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 = new librbd::ObjectMap<>(*ictx2, ictx2->snap_id);
+ C_SaferCond ctx;
+ object_map->open(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ std::shared_lock image_locker{ictx2->image_lock};
+ ASSERT_EQ(state, (*object_map)[0]);
+ object_map->put();
+ }
+ }
+}
+
+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(),
+ api::Io<>::write(*ictx, 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
+ std::unique_lock image_locker{ictx2->image_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(),
+ api::Io<>::read(*ictx2, 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(),
+ api::Io<>::write(*ictx, 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),
+ api::Io<>::discard(*ictx2, 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
+ std::unique_lock image_locker{ictx2->image_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(),
+ api::Io<>::read(*ictx2, i, bl.length(),
+ librbd::io::ReadResult{read_result}, 0));
+ ASSERT_TRUE(bl.contents_equal(read_bl));
+ }
+}
+
+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(),
+ api::Io<>::write(*ictx, 0, bl.length(), bufferlist{bl}, 0));
+ ASSERT_EQ(0, flush_writeback_cache(ictx));
+
+ 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", 0, no_op));
+
+ bufferlist write_full_bl;
+ write_full_bl.append(std::string(1 << ictx2->order, '2'));
+ ASSERT_EQ((ssize_t)write_full_bl.length(),
+ api::Io<>::write(*ictx2, 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(),
+ api::Io<>::read(*ictx2, 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(),
+ api::Io<>::read(*ictx2, 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.close());
+ ASSERT_EQ(0, m_rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+
+ 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(),
+ api::Io<>::write(*ictx, 0, bl.length(), bufferlist{bl}, 0));
+
+ ASSERT_EQ((ssize_t)bl.length(),
+ api::Io<>::write(*ictx, (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(),
+ api::Io<>::write(*ictx, (1 << ictx->order) * 10, bl.length(),
+ bufferlist{bl}, 0));
+
+ bufferlist bl2;
+ bl2.append(std::string(4096 - 1, '\0'));
+ ASSERT_EQ((ssize_t)bl2.length(),
+ api::Io<>::write(*ictx, (1 << ictx->order) * 10 + 4096 * 10,
+ bl2.length(), bufferlist{bl2}, 0));
+
+ ASSERT_EQ(0, flush_writeback_cache(ictx));
+
+ 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(),
+ api::Io<>::read(*ictx, (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(),
+ api::Io<>::write(*ictx, 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(),
+ api::Io<>::write(*ictx, (1 << ictx->order) * 10, bl.length(),
+ bufferlist{bl}, 0));
+ ASSERT_EQ(0, flush_writeback_cache(ictx));
+
+ 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(),
+ api::Io<>::read(*ictx, (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::api::Snapshot<>::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,
+ api::Io<>::read(*ictx, 0, 256,
+ librbd::io::ReadResult{read_result}, 0));
+ ASSERT_EQ(-ENODEV,
+ api::Io<>::write(*ictx, 0, bl.length(), bufferlist{bl}, 0));
+ ASSERT_EQ(-ENODEV, api::Io<>::discard(*ictx, 0, 1, 256));
+ ASSERT_EQ(-ENODEV,
+ api::Io<>::write_same(*ictx, 0, bl.length(), bufferlist{bl}, 0));
+ uint64_t mismatch_off;
+ ASSERT_EQ(-ENODEV,
+ api::Io<>::compare_and_write(*ictx, 0, bl.length(),
+ bufferlist{bl}, bufferlist{bl},
+ &mismatch_off, 0));
+ ASSERT_EQ(-ENODEV, api::Io<>::flush(*ictx));
+
+ 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));
+}
+
+} // namespace librbd
diff --git a/src/test/librbd/test_librbd.cc b/src/test/librbd/test_librbd.cc
new file mode 100644
index 000000000..76f9dab40
--- /dev/null
+++ b/src/test/librbd/test_librbd.cc
@@ -0,0 +1,9475 @@
+// -*- 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 "common/ceph_mutex.h"
+#include "json_spirit/json_spirit.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 <limits>
+
+#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);
+ }
+}
+
+
+
+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;
+}
+
+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";
+ }
+
+ bool is_skip_partial_discard_enabled(rbd_image_t image) {
+ if (is_skip_partial_discard_enabled()) {
+ rbd_flush(image);
+ uint64_t features;
+ EXPECT_EQ(0, rbd_get_features(image, &features));
+ return !(features & RBD_FEATURE_DIRTY_CACHE);
+ }
+ return false;
+ }
+
+ bool is_skip_partial_discard_enabled(librbd::Image& image) {
+ if (is_skip_partial_discard_enabled()) {
+ image.flush();
+ uint64_t features;
+ EXPECT_EQ(0, image.features(&features));
+ return !(features & RBD_FEATURE_DIRTY_CACHE);
+ }
+ return false;
+ }
+
+ 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;
+ }
+
+ void test_io(rbd_image_t image) {
+ bool skip_discard = is_skip_partial_discard_enabled(image);
+
+ 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);
+ }
+
+ 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;
+ std::mutex m_lock;
+ std::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)));
+ std::lock_guard<std::mutex> locker(m_lock);
+ m_size = info.size;
+ m_cond.notify_one();
+ }
+ void wait_for_size(size_t size) {
+ std::unique_lock<std::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)));
+ std::lock_guard<std::mutex> locker(m_lock);
+ m_size = info.size;
+ m_cond.notify_one();
+ }
+ void wait_for_size(size_t size) {
+ std::unique_lock<std::mutex> locker(m_lock);
+ ASSERT_TRUE(m_cond.wait_for(locker, seconds(5),
+ [size, this] {
+ return this->m_size == size;}));
+ }
+ librbd::Image &m_image;
+ std::mutex m_lock;
+ std::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, "key", 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, "key", 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, "key", 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, "key", 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, "key", 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, "key", 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, TestGetNameIdSnapPP)
+{
+ 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));
+
+ ASSERT_EQ(0, image.snap_create("snap1"));
+ ASSERT_EQ(0, image.snap_create("snap2"));
+ ASSERT_EQ(0, image.snap_create("snap3"));
+ vector<librbd::snap_info_t> snaps;
+ int r = image.snap_list(snaps);
+ EXPECT_TRUE(r >= 0);
+
+ for (size_t i = 0; i < snaps.size(); ++i) {
+ std::string expected_snap_name;
+ image.snap_get_name(snaps[i].id, &expected_snap_name);
+ ASSERT_EQ(expected_snap_name, snaps[i].name);
+ }
+
+ for (size_t i = 0; i < snaps.size(); ++i) {
+ uint64_t expected_snap_id;
+ image.snap_get_id(snaps[i].name, &expected_snap_id);
+ ASSERT_EQ(expected_snap_id, snaps[i].id);
+ }
+
+ ASSERT_EQ(0, image.snap_remove("snap1"));
+ ASSERT_EQ(0, image.snap_remove("snap2"));
+ ASSERT_EQ(0, image.snap_remove("snap3"));
+ 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();
+}
+
+TEST_F(TestLibRBD, ConcurrentCreatesUnvalidatedPool)
+{
+ rados_ioctx_t ioctx;
+ ASSERT_EQ(0, rados_ioctx_create(_cluster, create_pool(true).c_str(),
+ &ioctx));
+
+ std::vector<std::string> names;
+ for (int i = 0; i < 4; i++) {
+ names.push_back(get_temp_image_name());
+ }
+ uint64_t size = 2 << 20;
+
+ std::vector<std::thread> threads;
+ for (const auto& name : names) {
+ threads.emplace_back([ioctx, &name, size]() {
+ int order = 0;
+ ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order));
+ });
+ }
+ for (auto& thread : threads) {
+ thread.join();
+ }
+
+ for (const auto& name : names) {
+ ASSERT_EQ(0, rbd_remove(ioctx, name.c_str()));
+ }
+ rados_ioctx_destroy(ioctx);
+}
+
+static void remove_full_try(rados_ioctx_t ioctx, const std::string& image_name,
+ const std::string& data_pool_name)
+{
+ int order = 0;
+ uint64_t quota = 10 << 20;
+ uint64_t size = 5 * quota;
+ ASSERT_EQ(0, create_image(ioctx, image_name.c_str(), size, &order));
+
+ std::string cmdstr = "{\"prefix\": \"osd pool set-quota\", \"pool\": \"" +
+ data_pool_name + "\", \"field\": \"max_bytes\", \"val\": \"" +
+ std::to_string(quota) + "\"}";
+ char *cmd[1];
+ cmd[0] = (char *)cmdstr.c_str();
+ ASSERT_EQ(0, rados_mon_command(rados_ioctx_get_cluster(ioctx),
+ (const char **)cmd, 1, "", 0, nullptr, 0,
+ nullptr, 0));
+
+ rados_set_pool_full_try(ioctx);
+
+ rbd_image_t image;
+ ASSERT_EQ(0, rbd_open(ioctx, image_name.c_str(), &image, nullptr));
+
+ uint64_t off;
+ size_t len = 1 << 20;
+ ssize_t ret;
+ for (off = 0; off < size; off += len) {
+ ret = rbd_write_zeroes(image, off, len,
+ RBD_WRITE_ZEROES_FLAG_THICK_PROVISION,
+ LIBRADOS_OP_FLAG_FADVISE_FUA);
+ if (ret < 0) {
+ break;
+ }
+ ASSERT_EQ(ret, len);
+ sleep(1);
+ }
+ ASSERT_TRUE(off >= quota && off < size);
+ ASSERT_EQ(ret, -EDQUOT);
+
+ ASSERT_EQ(0, rbd_close(image));
+
+ // make sure we have latest map that marked the pool full
+ ASSERT_EQ(0, rados_wait_for_latest_osdmap(rados_ioctx_get_cluster(ioctx)));
+ ASSERT_EQ(0, rbd_remove(ioctx, image_name.c_str()));
+}
+
+TEST_F(TestLibRBD, RemoveFullTry)
+{
+ REQUIRE(!is_rbd_pwl_enabled((CephContext *)_rados.cct()));
+ REQUIRE(!is_librados_test_stub(_rados));
+
+ rados_ioctx_t ioctx;
+ auto pool_name = create_pool(true);
+ ASSERT_EQ(0, rados_ioctx_create(_cluster, pool_name.c_str(), &ioctx));
+ // cancel out rbd_default_data_pool -- we need an image without
+ // a separate data pool
+ ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "conf_rbd_default_data_pool",
+ pool_name.c_str()));
+
+ int order = 0;
+ auto image_name = get_temp_image_name();
+ // FIXME: this is a workaround for rbd_trash object being created
+ // on the first remove -- pre-create it to avoid bumping into quota
+ ASSERT_EQ(0, create_image(ioctx, image_name.c_str(), 0, &order));
+ ASSERT_EQ(0, rbd_remove(ioctx, image_name.c_str()));
+ remove_full_try(ioctx, image_name, pool_name);
+
+ rados_ioctx_destroy(ioctx);
+}
+
+TEST_F(TestLibRBD, RemoveFullTryDataPool)
+{
+ REQUIRE_FORMAT_V2();
+ REQUIRE(!is_rbd_pwl_enabled((CephContext *)_rados.cct()));
+ REQUIRE(!is_librados_test_stub(_rados));
+
+ rados_ioctx_t ioctx;
+ auto pool_name = create_pool(true);
+ auto data_pool_name = create_pool(true);
+ ASSERT_EQ(0, rados_ioctx_create(_cluster, pool_name.c_str(), &ioctx));
+ ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "conf_rbd_default_data_pool",
+ data_pool_name.c_str()));
+
+ auto image_name = get_temp_image_name();
+ remove_full_try(ioctx, image_name, data_pool_name);
+
+ rados_ioctx_destroy(ioctx);
+}
+
+TEST_F(TestLibRBD, TestIO)
+{
+ 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, rados_conf_set(_cluster, "rbd_read_from_replica_policy", "balance"));
+ ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+
+ test_io(image);
+
+ ASSERT_EQ(0, rbd_close(image));
+
+ rados_ioctx_destroy(ioctx);
+}
+
+TEST_F(TestLibRBD, TestEncryptionLUKS1)
+{
+ REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING));
+
+ rados_ioctx_t ioctx;
+ rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx);
+
+ int order = 0;
+ std::string name = get_temp_image_name();
+ uint64_t size = 32 << 20;
+
+ ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order));
+ ASSERT_EQ(0, rados_conf_set(
+ _cluster, "rbd_read_from_replica_policy", "balance"));
+
+ rbd_image_t image;
+ rbd_encryption_luks1_format_options_t opts = {
+ .alg = RBD_ENCRYPTION_ALGORITHM_AES256,
+ .passphrase = "password",
+ .passphrase_size = 8,
+ };
+ ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+
+#ifndef HAVE_LIBCRYPTSETUP
+ ASSERT_EQ(-ENOTSUP, rbd_encryption_format(
+ image, RBD_ENCRYPTION_FORMAT_LUKS1, &opts, sizeof(opts)));
+ ASSERT_EQ(-ENOTSUP, rbd_encryption_load(
+ image, RBD_ENCRYPTION_FORMAT_LUKS1, &opts, sizeof(opts)));
+#else
+ ASSERT_EQ(0, rbd_encryption_format(
+ image, RBD_ENCRYPTION_FORMAT_LUKS1, &opts, sizeof(opts)));
+ ASSERT_EQ(-EEXIST, rbd_encryption_load(
+ image, RBD_ENCRYPTION_FORMAT_LUKS1, &opts, sizeof(opts)));
+
+ test_io(image);
+
+ bool passed;
+ write_test_data(image, "test", 0, 4, 0, &passed);
+ ASSERT_TRUE(passed);
+ ASSERT_EQ(0, rbd_close(image));
+
+ ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+ ASSERT_EQ(0, rbd_encryption_load(
+ image, RBD_ENCRYPTION_FORMAT_LUKS1, &opts, sizeof(opts)));
+ read_test_data(image, "test", 0, 4, 0, &passed);
+ ASSERT_TRUE(passed);
+#endif
+
+ ASSERT_EQ(0, rbd_close(image));
+ rados_ioctx_destroy(ioctx);
+}
+
+TEST_F(TestLibRBD, TestEncryptionLUKS2)
+{
+ REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING));
+
+ rados_ioctx_t ioctx;
+ rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx);
+
+ int order = 0;
+ std::string name = get_temp_image_name();
+ uint64_t size = 32 << 20;
+
+ ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order));
+ ASSERT_EQ(0, rados_conf_set(
+ _cluster, "rbd_read_from_replica_policy", "balance"));
+
+ rbd_image_t image;
+ rbd_encryption_luks2_format_options_t opts = {
+ .alg = RBD_ENCRYPTION_ALGORITHM_AES256,
+ .passphrase = "password",
+ .passphrase_size = 8,
+ };
+ ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+
+#ifndef HAVE_LIBCRYPTSETUP
+ ASSERT_EQ(-ENOTSUP, rbd_encryption_format(
+ image, RBD_ENCRYPTION_FORMAT_LUKS2, &opts, sizeof(opts)));
+ ASSERT_EQ(-ENOTSUP, rbd_encryption_load(
+ image, RBD_ENCRYPTION_FORMAT_LUKS2, &opts, sizeof(opts)));
+#else
+ ASSERT_EQ(0, rbd_encryption_format(
+ image, RBD_ENCRYPTION_FORMAT_LUKS2, &opts, sizeof(opts)));
+ ASSERT_EQ(-EEXIST, rbd_encryption_load(
+ image, RBD_ENCRYPTION_FORMAT_LUKS2, &opts, sizeof(opts)));
+
+ test_io(image);
+
+ bool passed;
+ write_test_data(image, "test", 0, 4, 0, &passed);
+ ASSERT_TRUE(passed);
+ ASSERT_EQ(0, rbd_close(image));
+
+ ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+ ASSERT_EQ(0, rbd_encryption_load(
+ image, RBD_ENCRYPTION_FORMAT_LUKS2, &opts, sizeof(opts)));
+ read_test_data(image, "test", 0, 4, 0, &passed);
+ ASSERT_TRUE(passed);
+#endif
+
+ 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);
+
+ 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));
+
+ bool skip_discard = is_skip_partial_discard_enabled(image);
+
+ 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);
+
+ 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));
+
+ bool skip_discard = is_skip_partial_discard_enabled(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");
+ // These iovecs should produce a length overflow
+ struct iovec bad_iovs[] = {
+ {.iov_base = &write_buffer[0], .iov_len = 5},
+ {.iov_base = NULL, .iov_len = std::numeric_limits<size_t>::max()}
+ };
+ 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, 2, 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, 2, 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));
+
+ {
+ 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));
+
+ bool skip_discard = this->is_skip_partial_discard_enabled(image);
+
+ 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, TestSnapshotDeletedIo)
+{
+ 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;
+
+ int r;
+
+ ASSERT_EQ(0, create_image(ioctx, name.c_str(), isize, &order));
+ ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+ ASSERT_EQ(0, rbd_snap_create(image, "orig"));
+
+ r = rbd_snap_set(image, "orig");
+ ASSERT_EQ(r, 0);
+
+ ASSERT_EQ(0, rbd_snap_remove(image, "orig"));
+ char test[20];
+ ASSERT_EQ(-ENOENT, rbd_read(image, 20, 20, test));
+
+ r = rbd_snap_set(image, NULL);
+ ASSERT_EQ(r, 0);
+
+ 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, "key", 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, "key", 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;
+}
+
+TEST_F(TestLibRBD, SnapDiff)
+{
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ rados_ioctx_t ioctx;
+ rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx);
+
+ rbd_image_t image;
+ int order = 0;
+ std::string image_name = get_temp_image_name();
+ uint64_t size = 100 << 20;
+ ASSERT_EQ(0, create_image(ioctx, image_name.c_str(), size, &order));
+ ASSERT_EQ(0, rbd_open(ioctx, image_name.c_str(), &image, nullptr));
+
+ char test_data[TEST_IO_SIZE + 1];
+ for (size_t i = 0; i < TEST_IO_SIZE; ++i) {
+ test_data[i] = (char) (rand() % (126 - 33) + 33);
+ }
+ test_data[TEST_IO_SIZE] = '\0';
+
+ ASSERT_PASSED(write_test_data, image, test_data, 0,
+ TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE);
+
+ interval_set<uint64_t> diff;
+ ASSERT_EQ(0, rbd_diff_iterate2(image, nullptr, 0, size, true, true,
+ iterate_cb, &diff));
+ EXPECT_EQ(1 << order, diff.size());
+
+ ASSERT_EQ(0, rbd_snap_create(image, "snap1"));
+ ASSERT_EQ(0, rbd_snap_create(image, "snap2"));
+
+ diff.clear();
+ ASSERT_EQ(0, rbd_diff_iterate2(image, nullptr, 0, size, true, true,
+ iterate_cb, &diff));
+ EXPECT_EQ(1 << order, diff.size());
+
+ diff.clear();
+ ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, true,
+ iterate_cb, &diff));
+ EXPECT_EQ(0, diff.size());
+
+ diff.clear();
+ ASSERT_EQ(0, rbd_diff_iterate2(image, "snap2", 0, size, true, true,
+ iterate_cb, &diff));
+ EXPECT_EQ(0, diff.size());
+
+ ASSERT_EQ(0, rbd_snap_remove(image, "snap1"));
+ ASSERT_EQ(0, rbd_snap_remove(image, "snap2"));
+
+ ASSERT_EQ(0, rbd_close(image));
+ ASSERT_EQ(0, rbd_remove(ioctx, image_name.c_str()));
+
+ rados_ioctx_destroy(ioctx);
+}
+
+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_SUITE(DiffIterateTest, DiffIterateTypes);
+
+TYPED_TEST(DiffIterateTest, DiffIterate)
+{
+ 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));
+
+ bool skip_discard = this->is_skip_partial_discard_enabled(image);
+
+ 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)
+{
+ REQUIRE(!is_rbd_pwl_enabled((CephContext *)this->_rados.cct()));
+ 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 = 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));
+
+ bool skip_discard = this->is_skip_partial_discard_enabled(image);
+
+ 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, DiffIterateParent)
+{
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ 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 = 22;
+ std::string name = this->get_temp_image_name();
+ ssize_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 features;
+ ASSERT_EQ(0, image.features(&features));
+ uint64_t object_size = 0;
+ if (this->whole_object) {
+ object_size = 1 << order;
+ }
+
+ ceph::bufferlist bl;
+ bl.append(std::string(size, '1'));
+ ASSERT_EQ(size, image.write(0, size, bl));
+ ASSERT_EQ(0, image.snap_create("snap"));
+ ASSERT_EQ(0, 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;
+ ASSERT_EQ(0, rbd.open(ioctx, clone, clone_name.c_str(), NULL));
+
+ std::vector<diff_extent> extents;
+ ASSERT_EQ(0, clone.diff_iterate2(NULL, 0, size, true, this->whole_object,
+ vector_iterate_cb, &extents));
+ ASSERT_EQ(5u, extents.size());
+ ASSERT_EQ(diff_extent(0, 4194304, true, object_size), extents[0]);
+ ASSERT_EQ(diff_extent(4194304, 4194304, true, object_size), extents[1]);
+ ASSERT_EQ(diff_extent(8388608, 4194304, true, object_size), extents[2]);
+ ASSERT_EQ(diff_extent(12582912, 4194304, true, object_size), extents[3]);
+ ASSERT_EQ(diff_extent(16777216, 4194304, true, object_size), extents[4]);
+ extents.clear();
+
+ ASSERT_EQ(0, clone.resize(size / 2));
+ ASSERT_EQ(0, clone.resize(size));
+ ASSERT_EQ(1, clone.write(size - 1, 1, bl));
+
+ ASSERT_EQ(0, clone.diff_iterate2(NULL, 0, size, true, this->whole_object,
+ vector_iterate_cb, &extents));
+ ASSERT_EQ(4u, extents.size());
+ ASSERT_EQ(diff_extent(0, 4194304, true, object_size), extents[0]);
+ ASSERT_EQ(diff_extent(4194304, 4194304, true, object_size), extents[1]);
+ ASSERT_EQ(diff_extent(8388608, 2097152, true, object_size), extents[2]);
+ // hole (parent overlap = 10M) followed by copyup'ed object
+ ASSERT_EQ(diff_extent(16777216, 4194304, true, object_size), extents[3]);
+
+ ASSERT_PASSED(this->validate_object_map, image);
+ ASSERT_PASSED(this->validate_object_map, clone);
+ }
+
+ ioctx.close();
+}
+
+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));
+
+ 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));
+
+ bool skip_discard = this->is_skip_partial_discard_enabled(image);
+
+ uint64_t features;
+ ASSERT_EQ(0, image.features(&features));
+ 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(),
+ features, &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));
+
+ {
+ 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));
+
+ bool skip_discard = this->is_skip_partial_discard_enabled(image);
+
+ 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));
+
+ 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));
+
+ bool skip_discard = this->is_skip_partial_discard_enabled(image);
+
+ uint64_t features;
+ ASSERT_EQ(0, image.features(&features));
+ 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(), features, &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));
+}
+
+TYPED_TEST(DiffIterateTest, DiffIterateUnalignedSmall)
+{
+ 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();
+ ssize_t size = 10 << 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));
+
+ ceph::bufferlist bl;
+ bl.append(std::string(size, '1'));
+ ASSERT_EQ(size, image.write(0, size, bl));
+
+ std::vector<diff_extent> extents;
+ ASSERT_EQ(0, image.diff_iterate2(NULL, 5000005, 1234, true,
+ this->whole_object, vector_iterate_cb,
+ &extents));
+ ASSERT_EQ(1u, extents.size());
+ ASSERT_EQ(diff_extent(5000005, 1234, true, 0), extents[0]);
+
+ ASSERT_PASSED(this->validate_object_map, image);
+ }
+
+ ioctx.close();
+}
+
+TYPED_TEST(DiffIterateTest, DiffIterateUnaligned)
+{
+ 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 = 22;
+ std::string name = this->get_temp_image_name();
+ ssize_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));
+
+ ceph::bufferlist bl;
+ bl.append(std::string(size, '1'));
+ ASSERT_EQ(size, image.write(0, size, bl));
+
+ std::vector<diff_extent> extents;
+ ASSERT_EQ(0, image.diff_iterate2(NULL, 8376263, 4260970, true,
+ this->whole_object, vector_iterate_cb,
+ &extents));
+ ASSERT_EQ(3u, extents.size());
+ ASSERT_EQ(diff_extent(8376263, 12345, true, 0), extents[0]);
+ ASSERT_EQ(diff_extent(8388608, 4194304, true, 0), extents[1]);
+ ASSERT_EQ(diff_extent(12582912, 54321, true, 0), extents[2]);
+
+ ASSERT_PASSED(this->validate_object_map, image);
+ }
+
+ ioctx.close();
+}
+
+TYPED_TEST(DiffIterateTest, DiffIterateStriping)
+{
+ REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2);
+
+ librados::IoCtx ioctx;
+ ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx));
+
+ bool old_format;
+ uint64_t features;
+ ASSERT_EQ(0, get_features(&old_format, &features));
+ ASSERT_FALSE(old_format);
+
+ {
+ librbd::RBD rbd;
+ librbd::Image image;
+ int order = 22;
+ std::string name = this->get_temp_image_name();
+ ssize_t size = 24 << 20;
+
+ ASSERT_EQ(0, rbd.create3(ioctx, name.c_str(), size, features, &order,
+ 1 << 20, 3));
+ ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL));
+
+ ceph::bufferlist bl;
+ bl.append(std::string(size, '1'));
+ ASSERT_EQ(size, image.write(0, size, bl));
+
+ std::vector<diff_extent> extents;
+ ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object,
+ vector_iterate_cb, &extents));
+ ASSERT_EQ(2u, extents.size());
+ ASSERT_EQ(diff_extent(0, 12 << 20, true, 0), extents[0]);
+ ASSERT_EQ(diff_extent(12 << 20, 12 << 20, true, 0), extents[1]);
+ extents.clear();
+
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(size, image.discard(0, size));
+
+ ASSERT_EQ(0, image.diff_iterate2("one", 0, size, true, this->whole_object,
+ vector_iterate_cb, &extents));
+ ASSERT_EQ(2u, extents.size());
+ ASSERT_EQ(diff_extent(0, 12 << 20, false, 0), extents[0]);
+ ASSERT_EQ(diff_extent(12 << 20, 12 << 20, false, 0), extents[1]);
+ extents.clear();
+
+ ASSERT_EQ(1 << 20, image.write(0, 1 << 20, bl));
+ ASSERT_EQ(2 << 20, image.write(2 << 20, 2 << 20, bl));
+ ASSERT_EQ(2 << 20, image.write(5 << 20, 2 << 20, bl));
+ ASSERT_EQ(2 << 20, image.write(8 << 20, 2 << 20, bl));
+ ASSERT_EQ(13 << 20, image.write(11 << 20, 13 << 20, bl));
+
+ ASSERT_EQ(0, image.diff_iterate2("one", 0, size, true, this->whole_object,
+ vector_iterate_cb, &extents));
+ ASSERT_EQ(10u, extents.size());
+ ASSERT_EQ(diff_extent(0, 1 << 20, true, 0), extents[0]);
+ ASSERT_EQ(diff_extent(1 << 20, 1 << 20, false, 0), extents[1]);
+ ASSERT_EQ(diff_extent(2 << 20, 2 << 20, true, 0), extents[2]);
+ ASSERT_EQ(diff_extent(4 << 20, 1 << 20, false, 0), extents[3]);
+ ASSERT_EQ(diff_extent(5 << 20, 2 << 20, true, 0), extents[4]);
+ ASSERT_EQ(diff_extent(7 << 20, 1 << 20, false, 0), extents[5]);
+ ASSERT_EQ(diff_extent(8 << 20, 2 << 20, true, 0), extents[6]);
+ ASSERT_EQ(diff_extent(10 << 20, 1 << 20, false, 0), extents[7]);
+ ASSERT_EQ(diff_extent(11 << 20, 1 << 20, true, 0), extents[8]);
+ ASSERT_EQ(diff_extent(12 << 20, 12 << 20, true, 0), extents[9]);
+
+ ASSERT_PASSED(this->validate_object_map, image);
+ }
+
+ ioctx.close();
+}
+
+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));
+
+ ASSERT_EQ(0, rbd_invalidate_cache(image));
+
+ 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_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));
+
+ bool lock_owner;
+ ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner));
+ ASSERT_FALSE(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_FALSE(lock_owner);
+
+ bufferlist bl;
+ ASSERT_EQ(0, image1.write(0, 0, bl));
+ ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner));
+ ASSERT_TRUE(lock_owner);
+
+ name = new_name;
+ 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, "key", 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, "key", 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, "key", 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, "key", 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(-EROFS, rbd_metadata_set(image1, "key1", "value1"));
+ ASSERT_EQ(-EROFS, rbd_metadata_remove(image1, "key2"));
+
+ 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, "key", 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");
+
+ ASSERT_EQ(0, rbd_snap_set(image1, NULL));
+ 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, "key", 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, "key", 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, "key", 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, "key", 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, "key", 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, "key", 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("key", 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("key", 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("key", 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(-EROFS, image1.metadata_set("key1", "value1"));
+ ASSERT_EQ(-EROFS, image1.metadata_remove("key2"));
+ ASSERT_EQ(0, image1.metadata_list("key", 0, &pairs));
+ ASSERT_EQ(1U, pairs.size());
+ ASSERT_EQ(0, strncmp("value2", pairs["key2"].c_str(), 6));
+
+ ASSERT_EQ(0, image1.snap_set(NULL));
+ ASSERT_EQ(0, image1.metadata_set("key1", "value1"));
+ ASSERT_EQ(0, image1.metadata_set("key3", "value3"));
+ ASSERT_EQ(0, image1.metadata_list("key", 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("key", 0, &pairs));
+ ASSERT_EQ(4U, pairs.size());
+ pairs.clear();
+ ASSERT_EQ(0, image1.metadata_list("key", 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, FeaturesBitmaskString)
+{
+ librbd::RBD rbd;
+ uint64_t features = RBD_FEATURES_DEFAULT;
+
+ std::string features_str;
+ std::string expected_str = "deep-flatten,exclusive-lock,fast-diff,layering,object-map";
+ rbd.features_to_string(features, &features_str);
+ ASSERT_EQ(expected_str, features_str);
+
+ features = RBD_FEATURE_LAYERING;
+ features_str = "";
+ expected_str = "layering";
+ rbd.features_to_string(features, &features_str);
+ ASSERT_EQ(expected_str, features_str);
+
+ uint64_t features_bitmask;
+ features_str = "deep-flatten,exclusive-lock,fast-diff,layering,object-map";
+ rbd.features_from_string(features_str, &features_bitmask);
+ ASSERT_EQ(features_bitmask, RBD_FEATURES_DEFAULT);
+
+ features_str = "layering";
+ features_bitmask = 0;
+ rbd.features_from_string(features_str, &features_bitmask);
+ ASSERT_EQ(features_bitmask, RBD_FEATURE_LAYERING);
+}
+
+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));
+
+ 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));
+
+ bool skip_discard = this->is_skip_partial_discard_enabled(image);
+
+ 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, CreateWithMirrorEnabled) {
+ REQUIRE_FORMAT_V2();
+
+ 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));
+
+ librbd::ImageOptions image_options;
+ ASSERT_EQ(0, image_options.set(
+ RBD_IMAGE_OPTION_MIRROR_IMAGE_MODE,
+ static_cast<uint64_t>(RBD_MIRROR_IMAGE_MODE_SNAPSHOT)));
+
+ std::string parent_name = get_temp_image_name();
+ ASSERT_EQ(0, rbd.create4(ioctx, parent_name.c_str(), 2<<20, image_options));
+
+ librbd::Image parent_image;
+ ASSERT_EQ(0, rbd.open(ioctx, parent_image, parent_name.c_str(), NULL));
+
+ librbd::mirror_image_mode_t mirror_image_mode;
+ ASSERT_EQ(0, parent_image.mirror_image_get_mode(&mirror_image_mode));
+ ASSERT_EQ(RBD_MIRROR_IMAGE_MODE_SNAPSHOT, mirror_image_mode);
+
+ ASSERT_EQ(0, parent_image.snap_create("parent_snap"));
+ ASSERT_EQ(0, parent_image.snap_protect("parent_snap"));
+
+ std::string child_name = get_temp_image_name();
+ ASSERT_EQ(0, rbd.clone3(ioctx, parent_name.c_str(), "parent_snap", ioctx,
+ child_name.c_str(), image_options));
+
+ librbd::Image child_image;
+ ASSERT_EQ(0, rbd.open(ioctx, child_image, child_name.c_str(), NULL));
+
+ ASSERT_EQ(0, child_image.mirror_image_get_mode(&mirror_image_mode));
+ ASSERT_EQ(RBD_MIRROR_IMAGE_MODE_SNAPSHOT, mirror_image_mode);
+
+ ASSERT_EQ(0, child_image.mirror_image_disable(true));
+ ASSERT_EQ(0, parent_image.mirror_image_disable(true));
+ 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;
+ std::mutex lock;
+ const auto pingpong = [&](int m_id, rbd_image_t &m_image) {
+ for (int i = 0; i < 10; i++) {
+ {
+ std::lock_guard<std::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;
+ {
+ std::lock_guard<std::mutex> locker(lock);
+ owner_id = m_id;
+ }
+ usleep(rand() % 50000);
+ }
+
+ std::lock_guard<std::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);
+ REQUIRE(!is_rbd_pwl_enabled((CephContext *)_rados.cct()));
+
+ static char buf[10];
+
+ rados_t blocklist_cluster;
+ ASSERT_EQ("", connect_cluster(&blocklist_cluster));
+
+ rados_ioctx_t ioctx, blocklist_ioctx;
+ ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx));
+ ASSERT_EQ(0, rados_ioctx_create(blocklist_cluster, m_pool_name.c_str(),
+ &blocklist_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, blocklist_image;
+ ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+ ASSERT_EQ(0, rbd_open(blocklist_ioctx, name.c_str(), &blocklist_image, NULL));
+
+ ASSERT_EQ(0, rbd_metadata_set(image, "conf_rbd_blocklist_on_break_lock", "true"));
+ ASSERT_EQ(0, rbd_lock_acquire(blocklist_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(blocklist_cluster));
+
+ ASSERT_EQ((ssize_t)sizeof(buf), rbd_write(image, 0, sizeof(buf), buf));
+ ASSERT_EQ(-EBLOCKLISTED, rbd_write(blocklist_image, 0, sizeof(buf), buf));
+
+ ASSERT_EQ(0, rbd_close(image));
+ ASSERT_EQ(0, rbd_close(blocklist_image));
+
+ rbd_lock_get_owners_cleanup(lock_owners, max_lock_owners);
+
+ rados_ioctx_destroy(ioctx);
+ rados_ioctx_destroy(blocklist_ioctx);
+ rados_shutdown(blocklist_cluster);
+}
+
+TEST_F(TestLibRBD, DiscardAfterWrite)
+{
+ 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));
+
+ if (this->is_skip_partial_discard_enabled(image)) {
+ return;
+ }
+
+ // 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);
+
+ rbd_image_t image;
+ ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+ char source_spec[2048];
+ size_t source_spec_length = sizeof(source_spec);
+ ASSERT_EQ(0, rbd_get_migration_source_spec(image, source_spec,
+ &source_spec_length));
+ json_spirit::mValue json_source_spec;
+ json_spirit::read(source_spec, json_source_spec);
+ EXPECT_EQ(0, rbd_close(image));
+
+ 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()));
+
+ 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);
+
+ librbd::Image image;
+ ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL));
+ std::string source_spec;
+ ASSERT_EQ(0, image.get_migration_source_spec(&source_spec));
+ json_spirit::mValue json_source_spec;
+ json_spirit::read(source_spec, json_source_spec);
+ json_spirit::mObject json_source_spec_obj = json_source_spec.get_obj();
+ ASSERT_EQ("native", json_source_spec_obj["type"].get_str());
+ ASSERT_EQ(ioctx.get_id(), json_source_spec_obj["pool_id"].get_int64());
+ ASSERT_EQ("", json_source_spec_obj["pool_namespace"].get_str());
+ ASSERT_EQ(1, json_source_spec_obj.count("image_name"));
+ if (!old_format) {
+ ASSERT_EQ(1, json_source_spec_obj.count("image_id"));
+ }
+ ASSERT_EQ(0, image.close());
+
+ 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()));
+
+ 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);
+}
+
+void super_simple_write_cb_pp(librbd::completion_t cb, void *arg)
+{
+}
+
+TEST_F(TestLibRBD, DISABLED_TestSeqWriteAIOPP)
+{
+ librados::IoCtx ioctx;
+ ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx));
+
+ {
+ librbd::RBD rbd;
+ librbd::Image image;
+ int order = 21;
+ std::string name = get_temp_image_name();
+ uint64_t size = 5 * (1 << order);
+
+ 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) * 10];
+
+ for (int i = 0; i < 10; i++) {
+ for (uint64_t j = 0; j < TEST_IO_SIZE; j++) {
+ test_data[(TEST_IO_SIZE + 1) * i + j] = (char)(rand() % (126 - 33) + 33);
+ }
+ test_data[(TEST_IO_SIZE + 1) * i + TEST_IO_SIZE] = '\0';
+ }
+
+ struct timespec start_time;
+ clock_gettime(CLOCK_REALTIME, &start_time);
+
+ std::list<librbd::RBD::AioCompletion *> comps;
+ for (uint64_t i = 0; i < size / TEST_IO_SIZE; ++i) {
+ char *p = test_data + (TEST_IO_SIZE + 1) * (i % 10);
+ ceph::bufferlist bl;
+ bl.append(p, strlen(p));
+ auto comp = new librbd::RBD::AioCompletion(
+ NULL, (librbd::callback_t) super_simple_write_cb_pp);
+ image.aio_write(strlen(p) * i, strlen(p), bl, comp);
+ comps.push_back(comp);
+ if (i % 1000 == 0) {
+ cout << i << " reqs sent" << std::endl;
+ image.flush();
+ for (auto comp : comps) {
+ comp->wait_for_complete();
+ ASSERT_EQ(0, comp->get_return_value());
+ comp->release();
+ }
+ comps.clear();
+ }
+ }
+ int i = 0;
+ for (auto comp : comps) {
+ comp->wait_for_complete();
+ ASSERT_EQ(0, comp->get_return_value());
+ comp->release();
+ if (i % 1000 == 0) {
+ std::cout << i << " reqs completed" << std::endl;
+ }
+ i++;
+ }
+ comps.clear();
+
+ struct timespec end_time;
+ clock_gettime(CLOCK_REALTIME, &end_time);
+ int duration = end_time.tv_sec * 1000 + end_time.tv_nsec / 1000000 -
+ start_time.tv_sec * 1000 - start_time.tv_nsec / 1000000;
+ std::cout << "duration: " << duration << " msec" << std::endl;
+
+ for (uint64_t i = 0; i < size / TEST_IO_SIZE; ++i) {
+ char *p = test_data + (TEST_IO_SIZE + 1) * (i % 10);
+ ASSERT_PASSED(read_test_data, image, p, strlen(p) * i, TEST_IO_SIZE, 0);
+ }
+
+ ASSERT_PASSED(validate_object_map, image);
+ }
+
+ ioctx.close();
+}
+
+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, QuiesceWatch)
+{
+ rados_ioctx_t ioctx;
+ rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx);
+
+ 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_t image1, image2;
+ ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image1, NULL));
+ ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image2, NULL));
+
+ struct Watcher {
+ static void quiesce_cb(void *arg) {
+ Watcher *watcher = static_cast<Watcher *>(arg);
+ watcher->handle_quiesce();
+ }
+ static void unquiesce_cb(void *arg) {
+ Watcher *watcher = static_cast<Watcher *>(arg);
+ watcher->handle_unquiesce();
+ }
+
+ rbd_image_t &image;
+ uint64_t handle = 0;
+ size_t quiesce_count = 0;
+ size_t unquiesce_count = 0;
+
+ ceph::mutex lock = ceph::make_mutex("lock");
+ ceph::condition_variable cv;
+
+ Watcher(rbd_image_t &image) : image(image) {
+ }
+
+ void handle_quiesce() {
+ ASSERT_EQ(quiesce_count, unquiesce_count);
+ quiesce_count++;
+ rbd_quiesce_complete(image, handle, 0);
+ }
+ void handle_unquiesce() {
+ std::unique_lock locker(lock);
+ unquiesce_count++;
+ ASSERT_EQ(quiesce_count, unquiesce_count);
+ cv.notify_one();
+ }
+ bool wait_for_unquiesce(size_t c) {
+ std::unique_lock locker(lock);
+ return cv.wait_for(locker, seconds(60),
+ [this, c]() { return unquiesce_count >= c; });
+ }
+ } watcher1(image1), watcher2(image2);
+
+ ASSERT_EQ(0, rbd_quiesce_watch(image1, Watcher::quiesce_cb,
+ Watcher::unquiesce_cb, &watcher1,
+ &watcher1.handle));
+ ASSERT_EQ(0, rbd_quiesce_watch(image2, Watcher::quiesce_cb,
+ Watcher::unquiesce_cb, &watcher2,
+ &watcher2.handle));
+
+ ASSERT_EQ(0, rbd_snap_create(image1, "snap1"));
+ ASSERT_EQ(1U, watcher1.quiesce_count);
+ ASSERT_TRUE(watcher1.wait_for_unquiesce(1U));
+ ASSERT_EQ(1U, watcher2.quiesce_count);
+ ASSERT_TRUE(watcher2.wait_for_unquiesce(1U));
+
+ ASSERT_EQ(0, rbd_snap_create(image2, "snap2"));
+ ASSERT_EQ(2U, watcher1.quiesce_count);
+ ASSERT_TRUE(watcher1.wait_for_unquiesce(2U));
+ ASSERT_EQ(2U, watcher2.quiesce_count);
+ ASSERT_TRUE(watcher2.wait_for_unquiesce(2U));
+
+ ASSERT_EQ(0, rbd_quiesce_unwatch(image1, watcher1.handle));
+
+ ASSERT_EQ(0, rbd_snap_create(image1, "snap3"));
+ ASSERT_EQ(2U, watcher1.quiesce_count);
+ ASSERT_EQ(2U, watcher1.unquiesce_count);
+ ASSERT_EQ(3U, watcher2.quiesce_count);
+ ASSERT_TRUE(watcher2.wait_for_unquiesce(3U));
+
+ ASSERT_EQ(0, rbd_quiesce_unwatch(image2, watcher2.handle));
+
+ ASSERT_EQ(0, rbd_snap_remove(image1, "snap1"));
+ ASSERT_EQ(0, rbd_snap_remove(image1, "snap2"));
+ ASSERT_EQ(0, rbd_snap_remove(image1, "snap3"));
+ ASSERT_EQ(0, rbd_close(image1));
+ ASSERT_EQ(0, rbd_close(image2));
+ ASSERT_EQ(0, rbd_remove(ioctx, name.c_str()));
+ rados_ioctx_destroy(ioctx);
+}
+
+TEST_F(TestLibRBD, QuiesceWatchPP)
+{
+ 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 image1, image2;
+ ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL));
+ ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL));
+
+ struct Watcher : public librbd::QuiesceWatchCtx {
+ librbd::Image &image;
+ uint64_t handle = 0;
+ size_t quiesce_count = 0;
+ size_t unquiesce_count = 0;
+
+ ceph::mutex lock = ceph::make_mutex("lock");
+ ceph::condition_variable cv;
+
+ Watcher(librbd::Image &image) : image(image) {
+ }
+
+ void handle_quiesce() override {
+ ASSERT_EQ(quiesce_count, unquiesce_count);
+ quiesce_count++;
+ image.quiesce_complete(handle, 0);
+ }
+ void handle_unquiesce() override {
+ std::unique_lock locker(lock);
+ unquiesce_count++;
+ ASSERT_EQ(quiesce_count, unquiesce_count);
+ cv.notify_one();
+ }
+ bool wait_for_unquiesce(size_t c) {
+ std::unique_lock locker(lock);
+ return cv.wait_for(locker, seconds(60),
+ [this, c]() { return unquiesce_count >= c; });
+ }
+ } watcher1(image1), watcher2(image2);
+
+ ASSERT_EQ(0, image1.quiesce_watch(&watcher1, &watcher1.handle));
+ ASSERT_EQ(0, image2.quiesce_watch(&watcher2, &watcher2.handle));
+
+ ASSERT_EQ(0, image1.snap_create("snap1"));
+ ASSERT_EQ(1U, watcher1.quiesce_count);
+ ASSERT_TRUE(watcher1.wait_for_unquiesce(1U));
+ ASSERT_EQ(1U, watcher2.quiesce_count);
+ ASSERT_TRUE(watcher2.wait_for_unquiesce(1U));
+
+ ASSERT_EQ(0, image2.snap_create("snap2"));
+ ASSERT_EQ(2U, watcher1.quiesce_count);
+ ASSERT_TRUE(watcher1.wait_for_unquiesce(2U));
+ ASSERT_EQ(2U, watcher2.quiesce_count);
+ ASSERT_TRUE(watcher2.wait_for_unquiesce(2U));
+
+ ASSERT_EQ(0, image1.quiesce_unwatch(watcher1.handle));
+
+ ASSERT_EQ(0, image1.snap_create("snap3"));
+ ASSERT_EQ(2U, watcher1.quiesce_count);
+ ASSERT_EQ(2U, watcher1.unquiesce_count);
+ ASSERT_EQ(3U, watcher2.quiesce_count);
+ ASSERT_TRUE(watcher2.wait_for_unquiesce(3U));
+
+ ASSERT_EQ(0, image2.quiesce_unwatch(watcher2.handle));
+
+ ASSERT_EQ(0, image1.snap_remove("snap1"));
+ ASSERT_EQ(0, image1.snap_remove("snap2"));
+ ASSERT_EQ(0, image1.snap_remove("snap3"));
+ }
+
+ ASSERT_EQ(0, rbd.remove(ioctx, name.c_str()));
+ ioctx.close();
+}
+
+TEST_F(TestLibRBD, QuiesceWatchError)
+{
+ 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 image1, image2;
+ ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL));
+ ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL));
+
+ struct Watcher : public librbd::QuiesceWatchCtx {
+ librbd::Image &image;
+ int r;
+ uint64_t handle;
+ size_t quiesce_count = 0;
+ size_t unquiesce_count = 0;
+
+ ceph::mutex lock = ceph::make_mutex("lock");
+ ceph::condition_variable cv;
+
+ Watcher(librbd::Image &image, int r) : image(image), r(r) {
+ }
+
+ void reset_counters() {
+ quiesce_count = 0;
+ unquiesce_count = 0;
+ }
+
+ void handle_quiesce() override {
+ quiesce_count++;
+ image.quiesce_complete(handle, r);
+ }
+
+ void handle_unquiesce() override {
+ std::unique_lock locker(lock);
+ unquiesce_count++;
+ cv.notify_one();
+ }
+
+ bool wait_for_unquiesce() {
+ std::unique_lock locker(lock);
+ return cv.wait_for(locker, seconds(60),
+ [this]() {
+ return quiesce_count == unquiesce_count;
+ });
+ }
+ } watcher10(image1, -EINVAL), watcher11(image1, 0), watcher20(image2, 0);
+
+ ASSERT_EQ(0, image1.quiesce_watch(&watcher10, &watcher10.handle));
+ ASSERT_EQ(0, image1.quiesce_watch(&watcher11, &watcher11.handle));
+ ASSERT_EQ(0, image2.quiesce_watch(&watcher20, &watcher20.handle));
+
+ ASSERT_EQ(-EINVAL, image1.snap_create("snap1"));
+ ASSERT_GT(watcher10.quiesce_count, 0U);
+ ASSERT_EQ(watcher10.unquiesce_count, 0U);
+ ASSERT_GT(watcher11.quiesce_count, 0U);
+ ASSERT_TRUE(watcher11.wait_for_unquiesce());
+ ASSERT_GT(watcher20.quiesce_count, 0U);
+ ASSERT_TRUE(watcher20.wait_for_unquiesce());
+
+ PrintProgress prog_ctx;
+ watcher10.reset_counters();
+ watcher11.reset_counters();
+ watcher20.reset_counters();
+ ASSERT_EQ(0, image2.snap_create2("snap2",
+ RBD_SNAP_CREATE_IGNORE_QUIESCE_ERROR,
+ prog_ctx));
+ ASSERT_GT(watcher10.quiesce_count, 0U);
+ ASSERT_EQ(watcher10.unquiesce_count, 0U);
+ ASSERT_GT(watcher11.quiesce_count, 0U);
+ ASSERT_TRUE(watcher11.wait_for_unquiesce());
+ ASSERT_GT(watcher20.quiesce_count, 0U);
+ ASSERT_TRUE(watcher20.wait_for_unquiesce());
+
+ ASSERT_EQ(0, image1.quiesce_unwatch(watcher10.handle));
+
+ watcher11.reset_counters();
+ watcher20.reset_counters();
+ ASSERT_EQ(0, image1.snap_create("snap3"));
+ ASSERT_GT(watcher11.quiesce_count, 0U);
+ ASSERT_TRUE(watcher11.wait_for_unquiesce());
+ ASSERT_GT(watcher20.quiesce_count, 0U);
+ ASSERT_TRUE(watcher20.wait_for_unquiesce());
+
+ ASSERT_EQ(0, image1.quiesce_unwatch(watcher11.handle));
+
+ watcher20.reset_counters();
+ ASSERT_EQ(0, image2.snap_create2("snap4", RBD_SNAP_CREATE_SKIP_QUIESCE,
+ prog_ctx));
+ ASSERT_EQ(watcher20.quiesce_count, 0U);
+ ASSERT_EQ(watcher20.unquiesce_count, 0U);
+
+ ASSERT_EQ(0, image2.quiesce_unwatch(watcher20.handle));
+
+ ASSERT_EQ(0, image1.snap_remove("snap2"));
+ ASSERT_EQ(0, image1.snap_remove("snap3"));
+ ASSERT_EQ(0, image1.snap_remove("snap4"));
+ }
+
+ ASSERT_EQ(0, rbd.remove(ioctx, name.c_str()));
+ ioctx.close();
+}
+
+TEST_F(TestLibRBD, QuiesceWatchTimeout)
+{
+ REQUIRE(!is_librados_test_stub(_rados));
+
+ ASSERT_EQ(0, _rados.conf_set("rbd_quiesce_notification_attempts", "2"));
+
+ 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));
+
+ struct Watcher : public librbd::QuiesceWatchCtx {
+ librbd::Image &image;
+ std::mutex m_lock;
+ std::condition_variable m_cond;
+ size_t quiesce_count = 0;
+ size_t unquiesce_count = 0;
+
+ Watcher(librbd::Image &image) : image(image) {
+ }
+
+ void handle_quiesce() override {
+ std::lock_guard<std::mutex> locker(m_lock);
+ quiesce_count++;
+ m_cond.notify_one();
+ }
+
+ void handle_unquiesce() override {
+ std::lock_guard<std::mutex> locker(m_lock);
+ unquiesce_count++;
+ m_cond.notify_one();
+ }
+
+ void wait_for_quiesce() {
+ std::unique_lock<std::mutex> locker(m_lock);
+ ASSERT_TRUE(m_cond.wait_for(locker, seconds(60),
+ [this] {
+ return quiesce_count >= 1;
+ }));
+ }
+
+ void wait_for_unquiesce() {
+ std::unique_lock<std::mutex> locker(m_lock);
+ ASSERT_TRUE(m_cond.wait_for(locker, seconds(60),
+ [this] {
+ return quiesce_count == unquiesce_count;
+ }));
+ quiesce_count = unquiesce_count = 0;
+ }
+ } watcher(image);
+ uint64_t handle;
+
+ ASSERT_EQ(0, image.quiesce_watch(&watcher, &handle));
+
+ std::cerr << "test quiesce is not long enough to time out" << std::endl;
+
+ thread quiesce1([&image, &watcher, handle]() {
+ watcher.wait_for_quiesce();
+ sleep(8);
+ image.quiesce_complete(handle, 0);
+ });
+
+ ASSERT_EQ(0, image.snap_create("snap1"));
+ quiesce1.join();
+ ASSERT_GE(watcher.quiesce_count, 1U);
+ watcher.wait_for_unquiesce();
+
+ std::cerr << "test quiesce is timed out" << std::endl;
+
+ bool timed_out = false;
+ thread quiesce2([&image, &watcher, handle, &timed_out]() {
+ watcher.wait_for_quiesce();
+ for (int i = 0; !timed_out && i < 60; i++) {
+ std::cerr << "waiting for timed out ... " << i << std::endl;
+ sleep(1);
+ }
+ image.quiesce_complete(handle, 0);
+ });
+
+ ASSERT_EQ(-ETIMEDOUT, image.snap_create("snap2"));
+ timed_out = true;
+ quiesce2.join();
+ ASSERT_GE(watcher.quiesce_count, 1U);
+ watcher.wait_for_unquiesce();
+
+ thread quiesce3([&image, handle, &watcher]() {
+ watcher.wait_for_quiesce();
+ image.quiesce_complete(handle, 0);
+ });
+
+ std::cerr << "test retry succeeds" << std::endl;
+
+ ASSERT_EQ(0, image.snap_create("snap2"));
+ quiesce3.join();
+ ASSERT_GE(watcher.quiesce_count, 1U);
+ watcher.wait_for_unquiesce();
+
+ ASSERT_EQ(0, image.snap_remove("snap1"));
+ ASSERT_EQ(0, image.snap_remove("snap2"));
+ }
+
+ ASSERT_EQ(0, rbd.remove(ioctx, name.c_str()));
+ ioctx.close();
+}
+
+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));
+ auto expected_diff = interval_set<uint64_t>{{{0, 256}}};
+ ASSERT_EQ(expected_diff, diff);
+
+ // writes zero passed the current end extents.
+ // Now 1s from [0, 192) / length 192
+ ASSERT_EQ(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 = interval_set<uint64_t>{{{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 = interval_set<uint64_t>{{{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(size, image.read(0, size, read_bl));
+ EXPECT_EQ(expected_bl, read_bl);
+
+ ASSERT_EQ(0, image.close());
+}
+
+TEST_F(TestLibRBD, WriteZeroesThickProvision) {
+ 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));
+
+ interval_set<uint64_t> diff;
+ ASSERT_EQ(0, image.diff_iterate2(nullptr, 0, size, false, false,
+ iterate_cb, (void *)&diff));
+ auto expected_diff = interval_set<uint64_t>{{}};
+ ASSERT_EQ(expected_diff, diff);
+
+ // writes unaligned zeroes as a prepend
+ ASSERT_EQ(128, image.write_zeroes(
+ 0, 128, RBD_WRITE_ZEROES_FLAG_THICK_PROVISION, 0));
+ diff.clear();
+ ASSERT_EQ(0, image.diff_iterate2(nullptr, 0, size, false, false,
+ iterate_cb, (void *)&diff));
+ expected_diff = interval_set<uint64_t>{{{0, 128}}};
+ ASSERT_EQ(expected_diff, diff);
+
+ ASSERT_EQ(512, image.write_zeroes(
+ 384, 512, RBD_WRITE_ZEROES_FLAG_THICK_PROVISION, 0));
+ diff.clear();
+ ASSERT_EQ(0, image.diff_iterate2(nullptr, 0, size, false, false,
+ iterate_cb, (void *)&diff));
+ expected_diff = interval_set<uint64_t>{{{0, 896}}};
+ ASSERT_EQ(expected_diff, diff);
+
+ // prepend with write-same
+ ASSERT_EQ(640, image.write_zeroes(
+ 896, 640, RBD_WRITE_ZEROES_FLAG_THICK_PROVISION, 0));
+ diff.clear();
+ ASSERT_EQ(0, image.diff_iterate2(nullptr, 0, size, false, false,
+ iterate_cb, (void *)&diff));
+ expected_diff = interval_set<uint64_t>{{{0, 1536}}};
+ ASSERT_EQ(expected_diff, diff);
+
+ // write-same with append
+ ASSERT_EQ(640, image.write_zeroes(
+ 1536, 640, RBD_WRITE_ZEROES_FLAG_THICK_PROVISION, 0));
+ diff.clear();
+ ASSERT_EQ(0, image.diff_iterate2(nullptr, 0, size, false, false,
+ iterate_cb, (void *)&diff));
+ expected_diff = interval_set<uint64_t>{{{0, 2176}}};
+ ASSERT_EQ(expected_diff, diff);
+
+ // prepend + write-same + append
+ ASSERT_EQ(768, image.write_zeroes(
+ 2176, 768, RBD_WRITE_ZEROES_FLAG_THICK_PROVISION, 0));
+ diff.clear();
+ ASSERT_EQ(0, image.diff_iterate2(nullptr, 0, size, false, false,
+ iterate_cb, (void *)&diff));
+ expected_diff = interval_set<uint64_t>{{{0, 2944}}};
+
+ // write-same
+ ASSERT_EQ(1024, image.write_zeroes(
+ 3072, 1024, RBD_WRITE_ZEROES_FLAG_THICK_PROVISION, 0));
+ diff.clear();
+ ASSERT_EQ(0, image.diff_iterate2(nullptr, 0, size, false, false,
+ iterate_cb, (void *)&diff));
+ expected_diff = interval_set<uint64_t>{{{0, 4096}}};
+
+ bufferlist expected_bl;
+ expected_bl.append_zero(size);
+
+ bufferlist read_bl;
+ EXPECT_EQ(size, image.read(0, size, read_bl));
+ EXPECT_EQ(expected_bl, read_bl);
+
+ ASSERT_EQ(0, image.close());
+}
+
+TEST_F(TestLibRBD, ConcurentOperations)
+{
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ 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));
+
+ // Test creating/removing many snapshots simultaneously
+
+ std::vector<librbd::Image> images(10);
+ std::vector<librbd::RBD::AioCompletion *> comps;
+
+ for (auto &image : images) {
+ auto comp = new librbd::RBD::AioCompletion(NULL, NULL);
+ ASSERT_EQ(0, rbd.aio_open(ioctx, image, name.c_str(), NULL, comp));
+ comps.push_back(comp);
+ }
+
+ for (auto &comp : comps) {
+ ASSERT_EQ(0, comp->wait_for_complete());
+ ASSERT_EQ(1, comp->is_complete());
+ ASSERT_EQ(0, comp->get_return_value());
+ comp->release();
+ }
+ comps.clear();
+
+ std::vector<std::thread> threads;
+ int i = 0;
+ for (auto &image : images) {
+ std::string snap_name = "snap" + stringify(i++);
+ threads.emplace_back([&image, snap_name]() {
+ int r = image.snap_create(snap_name.c_str());
+ ceph_assert(r == 0);
+ });
+ }
+
+ for (auto &t : threads) {
+ t.join();
+ }
+ threads.clear();
+
+ i = 0;
+ for (auto &image : images) {
+ std::string snap_name = "snap" + stringify(i++);
+ threads.emplace_back([&image, snap_name](){
+ int r = image.snap_remove(snap_name.c_str());
+ ceph_assert(r == 0);
+ });
+ }
+
+ for (auto &t : threads) {
+ t.join();
+ }
+ threads.clear();
+
+ for (auto &image : images) {
+ auto comp = new librbd::RBD::AioCompletion(NULL, NULL);
+ ASSERT_EQ(0, image.aio_close(comp));
+ comps.push_back(comp);
+ }
+
+ for (auto &comp : comps) {
+ ASSERT_EQ(0, comp->wait_for_complete());
+ ASSERT_EQ(1, comp->is_complete());
+ ASSERT_EQ(0, comp->get_return_value());
+ comp->release();
+ }
+ comps.clear();
+
+ // Test shutdown
+ {
+ librbd::Image image1, image2, image3;
+ 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, rbd.open(ioctx, image3, name.c_str(), NULL));
+
+ ASSERT_EQ(0, image1.lock_acquire(RBD_LOCK_MODE_EXCLUSIVE));
+
+ struct Watcher : public librbd::QuiesceWatchCtx {
+ size_t count = 0;
+
+ ceph::mutex lock = ceph::make_mutex("lock");
+ ceph::condition_variable cv;
+
+ void handle_quiesce() override {
+ std::unique_lock locker(lock);
+ count++;
+ cv.notify_one();
+ }
+
+ void handle_unquiesce() override {
+ }
+
+ bool wait_for_quiesce(size_t c) {
+ std::unique_lock locker(lock);
+ return cv.wait_for(locker, seconds(60),
+ [this, c]() { return count >= c; });
+ }
+ } watcher;
+ uint64_t handle;
+ ASSERT_EQ(0, image2.quiesce_watch(&watcher, &handle));
+
+ auto close1_comp = new librbd::RBD::AioCompletion(NULL, NULL);
+
+ std::thread create_snap1([&image1, close1_comp]() {
+ int r = image1.snap_create("snap1");
+ ceph_assert(r == 0);
+ r = image1.aio_close(close1_comp);
+ ceph_assert(r == 0);
+ });
+
+ ASSERT_TRUE(watcher.wait_for_quiesce(1));
+
+ std::thread create_snap2([&image2]() {
+ int r = image2.snap_create("snap2");
+ ceph_assert(r == 0);
+ });
+
+ std::thread create_snap3([&image3]() {
+ int r = image3.snap_create("snap3");
+ ceph_assert(r == 0);
+ });
+
+ image2.quiesce_complete(handle, 0);
+ create_snap1.join();
+
+ ASSERT_TRUE(watcher.wait_for_quiesce(2));
+ image2.quiesce_complete(handle, 0);
+
+ ASSERT_TRUE(watcher.wait_for_quiesce(3));
+ image2.quiesce_complete(handle, 0);
+
+ ASSERT_EQ(0, close1_comp->wait_for_complete());
+ ASSERT_EQ(1, close1_comp->is_complete());
+ ASSERT_EQ(0, close1_comp->get_return_value());
+ close1_comp->release();
+
+ create_snap2.join();
+ create_snap3.join();
+
+ ASSERT_EQ(0, image2.quiesce_unwatch(handle));
+ ASSERT_EQ(0, image2.snap_remove("snap1"));
+ ASSERT_EQ(0, image2.snap_remove("snap2"));
+ ASSERT_EQ(0, image2.snap_remove("snap3"));
+ }
+
+ ASSERT_EQ(0, rbd.remove(ioctx, name.c_str()));
+ ioctx.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 000000000..2ff9f69de
--- /dev/null
+++ b/src/test/librbd/test_main.cc
@@ -0,0 +1,71 @@
+// -*- 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 << "warning: failed to enable lockdep" << std::endl;
+ }
+
+ 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 000000000..b6b3a31c7
--- /dev/null
+++ b/src/test/librbd/test_mirroring.cc
@@ -0,0 +1,1541 @@
+// -*- 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/api/Image.h"
+#include "librbd/api/Namespace.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ImageRequest.h"
+#include "librbd/journal/Types.h"
+#include "librbd/mirror/snapshot/GetImageStateRequest.h"
+#include "librbd/mirror/snapshot/RemoveImageStateRequest.h"
+#include "librbd/mirror/snapshot/SetImageStateRequest.h"
+#include "librbd/mirror/snapshot/UnlinkPeerRequest.h"
+#include "journal/Journaler.h"
+#include "journal/Settings.h"
+#include "common/Cond.h"
+#include <boost/scope_exit.hpp>
+#include <boost/assign/list_of.hpp>
+#include <utility>
+#include <vector>
+
+void register_test_mirroring() {
+}
+
+namespace librbd {
+
+static bool operator==(const mirror_peer_site_t& lhs,
+ const mirror_peer_site_t& rhs) {
+ return (lhs.uuid == rhs.uuid &&
+ lhs.direction == rhs.direction &&
+ lhs.site_name == rhs.site_name &&
+ lhs.client_name == rhs.client_name &&
+ lhs.last_seen == rhs.last_seen);
+}
+
+static std::ostream& operator<<(std::ostream& os,
+ const mirror_peer_site_t& rhs) {
+ os << "uuid=" << rhs.uuid << ", "
+ << "direction=" << rhs.direction << ", "
+ << "site_name=" << rhs.site_name << ", "
+ << "client_name=" << rhs.client_name << ", "
+ << "last_seen=" << rhs.last_seen;
+ return os;
+}
+
+};
+
+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";
+
+ int get_local_mirror_image_site_status(
+ const librbd::mirror_image_global_status_t& status,
+ librbd::mirror_image_site_status_t* local_status) {
+ auto it = std::find_if(status.site_statuses.begin(),
+ status.site_statuses.end(),
+ [](auto& site_status) {
+ return (site_status.mirror_uuid ==
+ RBD_MIRROR_IMAGE_STATUS_LOCAL_MIRROR_UUID);
+ });
+ if (it == status.site_statuses.end()) {
+ return -ENOENT;
+ }
+
+ *local_status = *it;
+ return 0;
+ }
+
+ void check_mirror_image_enable(
+ rbd_mirror_mode_t mirror_mode, uint64_t features, int expected_r,
+ rbd_mirror_image_state_t mirror_state,
+ rbd_mirror_image_mode_t mirror_image_mode = RBD_MIRROR_IMAGE_MODE_JOURNAL) {
+
+ 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_enable2(mirror_image_mode));
+
+ 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);
+
+ if (mirror_image.state == RBD_MIRROR_IMAGE_ENABLED) {
+ librbd::mirror_image_mode_t mode;
+ ASSERT_EQ(0, image.mirror_image_get_mode(&mode));
+ ASSERT_EQ(mirror_image_mode, mode);
+ }
+
+ librbd::mirror_image_global_status_t status;
+ ASSERT_EQ(0, image.mirror_image_get_global_status(&status, sizeof(status)));
+ librbd::mirror_image_site_status_t local_status;
+ ASSERT_EQ(0, get_local_mirror_image_site_status(status, &local_status));
+ ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, local_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_global_status_t status;
+ ASSERT_EQ(0, image.mirror_image_get_global_status(&status, sizeof(status)));
+ librbd::mirror_image_site_status_t local_status;
+ ASSERT_EQ(0, get_local_mirror_image_site_status(status, &local_status));
+ ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, local_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_global_status_t> images;
+ ASSERT_EQ(0, m_rbd.mirror_image_global_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_global_status_t status;
+ ASSERT_EQ(0, image.mirror_image_get_global_status(&status, sizeof(status)));
+ librbd::mirror_image_site_status_t local_status;
+ ASSERT_EQ(0, get_local_mirror_image_site_status(status, &local_status));
+ ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, local_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,
+ rbd_mirror_image_mode_t mirror_image_mode = RBD_MIRROR_IMAGE_MODE_JOURNAL) {
+
+ 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_enable2(mirror_image_mode));
+ }
+
+ 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);
+
+ if (mirror_image.state == RBD_MIRROR_IMAGE_ENABLED) {
+ librbd::mirror_image_mode_t mode;
+ ASSERT_EQ(0, image.mirror_image_get_mode(&mode));
+ ASSERT_EQ(mirror_image_mode, mode);
+ }
+
+ librbd::mirror_image_global_status_t status;
+ ASSERT_EQ(0, image.mirror_image_get_global_status(&status, sizeof(status)));
+ librbd::mirror_image_site_status_t local_status;
+ ASSERT_EQ(0, get_local_mirror_image_site_status(status, &local_status));
+ ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, local_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_global_status_t status;
+ ASSERT_EQ(0, image.mirror_image_get_global_status(&status,
+ sizeof(status)));
+ librbd::mirror_image_site_status_t local_status;
+ ASSERT_EQ(0, get_local_mirror_image_site_status(status, &local_status));
+ ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, local_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_enable2(RBD_MIRROR_IMAGE_MODE_JOURNAL));
+ }
+
+ 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_enable2(RBD_MIRROR_IMAGE_MODE_JOURNAL));
+ }
+
+ 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", {}, nullptr);
+ 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, RBD_MIRROR_IMAGE_MODE_JOURNAL);
+}
+
+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, RBD_MIRROR_IMAGE_MODE_JOURNAL);
+}
+
+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_MirrorModeImage_NoObjectMap) {
+ uint64_t features = 0;
+ 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_enable2(RBD_MIRROR_IMAGE_MODE_JOURNAL));
+
+ 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_global_status_t status;
+ ASSERT_EQ(0, image.mirror_image_get_global_status(&status, sizeof(status)));
+ librbd::mirror_image_site_status_t local_status;
+ ASSERT_EQ(0, get_local_mirror_image_site_status(status, &local_status));
+ ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, local_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_global_status_t status;
+ ASSERT_EQ(0, image.mirror_image_get_global_status(&status, sizeof(status)));
+ librbd::mirror_image_site_status_t local_status;
+ ASSERT_EQ(0, get_local_mirror_image_site_status(status, &local_status));
+ ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, local_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_In_MirrorModeDisabled_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, EnableImageMirror_In_MirrorModePool_WithoutJournaling) {
+ uint64_t features = 0;
+ features |= RBD_FEATURE_OBJECT_MAP;
+ features |= RBD_FEATURE_EXCLUSIVE_LOCK;
+ check_mirror_image_enable(RBD_MIRROR_MODE_POOL, features, -EINVAL,
+ RBD_MIRROR_IMAGE_DISABLED);
+}
+
+TEST_F(TestMirroring, EnableImageMirror_In_MirrorModeImage_WithoutJournaling) {
+ uint64_t features = 0;
+ features |= RBD_FEATURE_OBJECT_MAP;
+ features |= RBD_FEATURE_EXCLUSIVE_LOCK;
+ check_mirror_image_enable(RBD_MIRROR_MODE_IMAGE, features, 0,
+ RBD_MIRROR_IMAGE_ENABLED, RBD_MIRROR_IMAGE_MODE_SNAPSHOT);
+}
+
+TEST_F(TestMirroring, EnableImageMirror_In_MirrorModeImage_WithoutExclusiveLock) {
+ uint64_t features = 0;
+ check_mirror_image_enable(RBD_MIRROR_MODE_IMAGE, features, 0,
+ RBD_MIRROR_IMAGE_ENABLED, RBD_MIRROR_IMAGE_MODE_SNAPSHOT);
+}
+
+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_MirrorModeImage_SnapshotMirroringEnabled) {
+ 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, true, features,
+ 0, RBD_MIRROR_MODE_IMAGE, RBD_MIRROR_IMAGE_ENABLED,
+ RBD_MIRROR_IMAGE_MODE_SNAPSHOT);
+}
+
+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,
+ RBD_MIRROR_IMAGE_MODE_JOURNAL);
+}
+
+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,
+ RBD_MIRROR_IMAGE_MODE_JOURNAL);
+}
+
+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_global_status_t> images;
+ ASSERT_EQ(0, m_rbd.mirror_image_global_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_global_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_global_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_global_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_enable2(
+ RBD_MIRROR_IMAGE_MODE_JOURNAL));
+ }
+
+ // 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_global_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_global_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);
+
+ librbd::mirror_image_site_status_t local_status;
+ ASSERT_EQ(0, get_local_mirror_image_site_status(status, &local_status));
+ ASSERT_EQ(MIRROR_IMAGE_STATUS_STATE_UNKNOWN, local_status.state);
+ ASSERT_EQ("status not found", local_status.description);
+ ASSERT_FALSE(local_status.up);
+ ASSERT_EQ(0, local_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));
+}
+
+TEST_F(TestMirroring, PeerDirection) {
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_POOL));
+
+ std::string uuid;
+ ASSERT_EQ(-EINVAL, m_rbd.mirror_peer_site_add(
+ m_ioctx, &uuid, RBD_MIRROR_PEER_DIRECTION_TX, "siteA",
+ "client.admin"));
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_add(m_ioctx, &uuid,
+ RBD_MIRROR_PEER_DIRECTION_RX_TX,
+ "siteA", "client.admin"));
+
+ std::vector<librbd::mirror_peer_site_t> peers;
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_list(m_ioctx, &peers));
+ std::vector<librbd::mirror_peer_site_t> expected_peers = {
+ {uuid, RBD_MIRROR_PEER_DIRECTION_RX_TX, "siteA", "", "client.admin", 0}};
+ ASSERT_EQ(expected_peers, peers);
+
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_set_direction(
+ m_ioctx, uuid, RBD_MIRROR_PEER_DIRECTION_RX));
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_list(m_ioctx, &peers));
+ expected_peers = {
+ {uuid, RBD_MIRROR_PEER_DIRECTION_RX, "siteA", "", "client.admin", 0}};
+ ASSERT_EQ(expected_peers, peers);
+
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_remove(m_ioctx, uuid));
+}
+
+TEST_F(TestMirroring, Snapshot)
+{
+ REQUIRE_FORMAT_V2();
+
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
+
+ uint64_t features;
+ ASSERT_TRUE(get_features(&features));
+ 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.metadata_set(
+ "conf_rbd_mirroring_max_mirroring_snapshots", "5"));
+
+ uint64_t snap_id;
+
+ ASSERT_EQ(-EINVAL, image.mirror_image_create_snapshot(&snap_id));
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE));
+ ASSERT_EQ(-EINVAL, image.mirror_image_create_snapshot(&snap_id));
+ ASSERT_EQ(0, image.mirror_image_enable2(RBD_MIRROR_IMAGE_MODE_SNAPSHOT));
+ librbd::mirror_image_mode_t mode;
+ ASSERT_EQ(0, image.mirror_image_get_mode(&mode));
+ ASSERT_EQ(RBD_MIRROR_IMAGE_MODE_SNAPSHOT, mode);
+ ASSERT_EQ(-EINVAL, image.mirror_image_create_snapshot(&snap_id));
+ std::string peer_uuid;
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_add(m_ioctx, &peer_uuid,
+ RBD_MIRROR_PEER_DIRECTION_RX_TX,
+ "cluster", "client"));
+ // The mirroring was enabled when no peer was configured. Therefore, the
+ // initial snapshot has no peers linked and will be removed after the
+ // creation of a new mirror snapshot.
+ ASSERT_EQ(0, image.mirror_image_create_snapshot(&snap_id));
+ vector<librbd::snap_info_t> snaps;
+ ASSERT_EQ(0, image.snap_list(snaps));
+ ASSERT_EQ(1U, snaps.size());
+ ASSERT_EQ(snaps[0].id, snap_id);
+
+ for (int i = 0; i < 5; i++) {
+ ASSERT_EQ(0, image.mirror_image_create_snapshot(&snap_id));
+ }
+ snaps.clear();
+ ASSERT_EQ(0, image.snap_list(snaps));
+ ASSERT_EQ(5U, snaps.size());
+ ASSERT_EQ(snaps[4].id, snap_id);
+
+ // automatic peer unlink on max_mirroring_snapshots reached
+ ASSERT_EQ(0, image.mirror_image_create_snapshot(&snap_id));
+ vector<librbd::snap_info_t> snaps1;
+ ASSERT_EQ(0, image.snap_list(snaps1));
+ ASSERT_EQ(5U, snaps1.size());
+ ASSERT_EQ(snaps1[0].id, snaps[0].id);
+ ASSERT_EQ(snaps1[1].id, snaps[1].id);
+ ASSERT_EQ(snaps1[2].id, snaps[2].id);
+ ASSERT_EQ(snaps1[3].id, snaps[3].id);
+ ASSERT_EQ(snaps1[4].id, snap_id);
+
+ librbd::snap_namespace_type_t snap_ns_type;
+ ASSERT_EQ(0, image.snap_get_namespace_type(snap_id, &snap_ns_type));
+ ASSERT_EQ(RBD_SNAP_NAMESPACE_TYPE_MIRROR, snap_ns_type);
+ librbd::snap_mirror_namespace_t mirror_snap;
+ ASSERT_EQ(0, image.snap_get_mirror_namespace(snap_id, &mirror_snap,
+ sizeof(mirror_snap)));
+ ASSERT_EQ(1U, mirror_snap.mirror_peer_uuids.size());
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer_uuid));
+
+ for (auto &snap : snaps1) {
+ ASSERT_EQ(0, image.snap_remove_by_id(snap.id));
+ }
+
+ ASSERT_EQ(0, image.close());
+ ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_remove(m_ioctx, peer_uuid));
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
+}
+
+TEST_F(TestMirroring, SnapshotRemoveOnDisable)
+{
+ REQUIRE_FORMAT_V2();
+
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE));
+ std::string peer_uuid;
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_add(m_ioctx, &peer_uuid,
+ RBD_MIRROR_PEER_DIRECTION_RX_TX,
+ "cluster", "client"));
+
+ uint64_t features;
+ ASSERT_TRUE(get_features(&features));
+ 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_enable2(RBD_MIRROR_IMAGE_MODE_SNAPSHOT));
+ uint64_t snap_id;
+ ASSERT_EQ(0, image.mirror_image_create_snapshot(&snap_id));
+
+ vector<librbd::snap_info_t> snaps;
+ ASSERT_EQ(0, image.snap_list(snaps));
+ ASSERT_EQ(2U, snaps.size());
+ ASSERT_EQ(snaps[1].id, snap_id);
+
+ ASSERT_EQ(0, image.mirror_image_disable(false));
+
+ snaps.clear();
+ ASSERT_EQ(0, image.snap_list(snaps));
+ ASSERT_TRUE(snaps.empty());
+
+ ASSERT_EQ(0, image.close());
+ ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_remove(m_ioctx, peer_uuid));
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
+}
+
+TEST_F(TestMirroring, SnapshotUnlinkPeer)
+{
+ REQUIRE_FORMAT_V2();
+
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE));
+ std::string peer1_uuid;
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_add(m_ioctx, &peer1_uuid,
+ RBD_MIRROR_PEER_DIRECTION_RX_TX,
+ "cluster1", "client"));
+ std::string peer2_uuid;
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_add(m_ioctx, &peer2_uuid,
+ RBD_MIRROR_PEER_DIRECTION_RX_TX,
+ "cluster2", "client"));
+ std::string peer3_uuid;
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_add(m_ioctx, &peer3_uuid,
+ RBD_MIRROR_PEER_DIRECTION_RX_TX,
+ "cluster3", "client"));
+ uint64_t features;
+ ASSERT_TRUE(get_features(&features));
+ features &= ~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_enable2(RBD_MIRROR_IMAGE_MODE_SNAPSHOT));
+ uint64_t snap_id;
+ ASSERT_EQ(0, image.mirror_image_create_snapshot(&snap_id));
+ uint64_t snap_id2;
+ ASSERT_EQ(0, image.mirror_image_create_snapshot(&snap_id2));
+ librbd::snap_mirror_namespace_t mirror_snap;
+ ASSERT_EQ(0, image.snap_get_mirror_namespace(snap_id, &mirror_snap,
+ sizeof(mirror_snap)));
+ ASSERT_EQ(3U, mirror_snap.mirror_peer_uuids.size());
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer1_uuid));
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer2_uuid));
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer3_uuid));
+
+ auto ictx = new librbd::ImageCtx(image_name, "", nullptr, m_ioctx, false);
+ ASSERT_EQ(0, ictx->state->open(0));
+ BOOST_SCOPE_EXIT(&ictx) {
+ if (ictx != nullptr) {
+ ictx->state->close();
+ }
+ } BOOST_SCOPE_EXIT_END;
+
+ C_SaferCond cond1;
+ auto req = librbd::mirror::snapshot::UnlinkPeerRequest<>::create(
+ ictx, snap_id, peer1_uuid, &cond1);
+ req->send();
+ ASSERT_EQ(0, cond1.wait());
+
+ ASSERT_EQ(0, image.snap_get_mirror_namespace(snap_id, &mirror_snap,
+ sizeof(mirror_snap)));
+ ASSERT_EQ(2U, mirror_snap.mirror_peer_uuids.size());
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer2_uuid));
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer3_uuid));
+
+ ASSERT_EQ(0, librbd::api::Namespace<>::create(m_ioctx, "ns1"));
+ librados::IoCtx ns_ioctx;
+ ns_ioctx.dup(m_ioctx);
+ ns_ioctx.set_namespace("ns1");
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(ns_ioctx, RBD_MIRROR_MODE_IMAGE));
+ ASSERT_EQ(0, m_rbd.create2(ns_ioctx, image_name.c_str(), 4096, features,
+ &order));
+
+ librbd::Image ns_image;
+ ASSERT_EQ(0, m_rbd.open(ns_ioctx, ns_image, image_name.c_str()));
+ ASSERT_EQ(0, ns_image.mirror_image_enable2(RBD_MIRROR_IMAGE_MODE_SNAPSHOT));
+ uint64_t ns_snap_id;
+ ASSERT_EQ(0, ns_image.mirror_image_create_snapshot(&ns_snap_id));
+ ASSERT_EQ(0, ns_image.snap_get_mirror_namespace(ns_snap_id, &mirror_snap,
+ sizeof(mirror_snap)));
+ ASSERT_EQ(3U, mirror_snap.mirror_peer_uuids.size());
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer1_uuid));
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer2_uuid));
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer3_uuid));
+
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_remove(m_ioctx, peer3_uuid));
+
+ ASSERT_EQ(0, image.snap_get_mirror_namespace(snap_id, &mirror_snap,
+ sizeof(mirror_snap)));
+ ASSERT_EQ(1U, mirror_snap.mirror_peer_uuids.size());
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer2_uuid));
+
+ ASSERT_EQ(0, ns_image.snap_get_mirror_namespace(ns_snap_id, &mirror_snap,
+ sizeof(mirror_snap)));
+ ASSERT_EQ(2U, mirror_snap.mirror_peer_uuids.size());
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer1_uuid));
+ ASSERT_EQ(1, mirror_snap.mirror_peer_uuids.count(peer2_uuid));
+
+ C_SaferCond cond2;
+ req = librbd::mirror::snapshot::UnlinkPeerRequest<>::create(
+ ictx, snap_id, peer2_uuid, &cond2);
+ req->send();
+ ASSERT_EQ(0, cond2.wait());
+
+ ASSERT_EQ(-ENOENT, image.snap_get_mirror_namespace(snap_id, &mirror_snap,
+ sizeof(mirror_snap)));
+ ictx->state->close();
+ ictx = nullptr;
+ ASSERT_EQ(0, image.close());
+ ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
+
+ ASSERT_EQ(0, ns_image.close());
+ ASSERT_EQ(0, m_rbd.remove(ns_ioctx, image_name.c_str()));
+ ASSERT_EQ(0, librbd::api::Namespace<>::remove(m_ioctx, "ns1"));
+
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_remove(m_ioctx, peer1_uuid));
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_remove(m_ioctx, peer2_uuid));
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
+}
+
+TEST_F(TestMirroring, SnapshotImageState)
+{
+ REQUIRE_FORMAT_V2();
+
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE));
+
+ uint64_t features;
+ ASSERT_TRUE(get_features(&features));
+ 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.snap_create("snap"));
+ ASSERT_EQ(0, image.mirror_image_enable2(RBD_MIRROR_IMAGE_MODE_SNAPSHOT));
+ std::vector<librbd::snap_info_t> snaps;
+ ASSERT_EQ(0, image.snap_list(snaps));
+ ASSERT_EQ(2U, snaps.size());
+ auto snap_id = snaps[1].id;
+
+ auto ictx = new librbd::ImageCtx(image_name, "", nullptr, m_ioctx, false);
+ ASSERT_EQ(0, ictx->state->open(0));
+ BOOST_SCOPE_EXIT(&ictx) {
+ if (ictx != nullptr) {
+ ictx->state->close();
+ }
+ } BOOST_SCOPE_EXIT_END;
+
+ {
+ C_SaferCond cond;
+ auto req = librbd::mirror::snapshot::SetImageStateRequest<>::create(
+ ictx, snap_id, &cond);
+ req->send();
+ ASSERT_EQ(0, cond.wait());
+ }
+
+ librbd::mirror::snapshot::ImageState image_state;
+ {
+ C_SaferCond cond;
+ auto req = librbd::mirror::snapshot::GetImageStateRequest<>::create(
+ ictx, snap_id, &image_state, &cond);
+ req->send();
+ ASSERT_EQ(0, cond.wait());
+ }
+
+ ASSERT_EQ(image_name, image_state.name);
+ ASSERT_EQ(0, image.features(&features));
+ ASSERT_EQ(features & ~RBD_FEATURES_IMPLICIT_ENABLE, image_state.features);
+ ASSERT_EQ(1U, image_state.snapshots.size());
+ ASSERT_EQ("snap", image_state.snapshots.begin()->second.name);
+ uint8_t original_pairs_num = image_state.metadata.size();
+
+ {
+ C_SaferCond cond;
+ auto req = librbd::mirror::snapshot::RemoveImageStateRequest<>::create(
+ ictx, snap_id, &cond);
+ req->send();
+ ASSERT_EQ(0, cond.wait());
+ }
+
+ // test storing "large" image state in multiple objects
+
+ ASSERT_EQ(0, ictx->config.set_val("rbd_default_order", "8"));
+
+ for (int i = 0; i < 10; i++) {
+ ASSERT_EQ(0, image.metadata_set(stringify(i), std::string(1024, 'A' + i)));
+ }
+
+ {
+ C_SaferCond cond;
+ auto req = librbd::mirror::snapshot::SetImageStateRequest<>::create(
+ ictx, snap_id, &cond);
+ req->send();
+ ASSERT_EQ(0, cond.wait());
+ }
+
+ {
+ C_SaferCond cond;
+ auto req = librbd::mirror::snapshot::GetImageStateRequest<>::create(
+ ictx, snap_id, &image_state, &cond);
+ req->send();
+ ASSERT_EQ(0, cond.wait());
+ }
+
+ ASSERT_EQ(image_name, image_state.name);
+ ASSERT_EQ(features & ~RBD_FEATURES_IMPLICIT_ENABLE, image_state.features);
+ ASSERT_EQ(original_pairs_num + 10, image_state.metadata.size());
+ for (int i = 0; i < 10; i++) {
+ auto &bl = image_state.metadata[stringify(i)];
+ ASSERT_EQ(0, strncmp(std::string(1024, 'A' + i).c_str(), bl.c_str(),
+ bl.length()));
+ }
+
+ {
+ C_SaferCond cond;
+ auto req = librbd::mirror::snapshot::RemoveImageStateRequest<>::create(
+ ictx, snap_id, &cond);
+ req->send();
+ ASSERT_EQ(0, cond.wait());
+ }
+
+ ASSERT_EQ(0, ictx->state->close());
+ ictx = nullptr;
+
+ ASSERT_EQ(0, image.snap_remove("snap"));
+ ASSERT_EQ(0, image.close());
+ ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
+}
+
+TEST_F(TestMirroring, SnapshotPromoteDemote)
+{
+ REQUIRE_FORMAT_V2();
+
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE));
+ std::string peer_uuid;
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_add(m_ioctx, &peer_uuid,
+ RBD_MIRROR_PEER_DIRECTION_RX_TX,
+ "cluster", "client"));
+
+ uint64_t features;
+ ASSERT_TRUE(get_features(&features));
+ 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_enable2(RBD_MIRROR_IMAGE_MODE_SNAPSHOT));
+ librbd::mirror_image_mode_t mode;
+ ASSERT_EQ(0, image.mirror_image_get_mode(&mode));
+ ASSERT_EQ(RBD_MIRROR_IMAGE_MODE_SNAPSHOT, mode);
+
+ ASSERT_EQ(-EINVAL, image.mirror_image_promote(false));
+ ASSERT_EQ(0, image.mirror_image_demote());
+ ASSERT_EQ(0, image.mirror_image_promote(false));
+ ASSERT_EQ(0, image.mirror_image_demote());
+ ASSERT_EQ(0, image.mirror_image_promote(false));
+
+ ASSERT_EQ(0, image.close());
+ ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_remove(m_ioctx, peer_uuid));
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
+}
+
+TEST_F(TestMirroring, AioSnapshotCreate)
+{
+ REQUIRE_FORMAT_V2();
+
+ 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));
+ std::string peer_uuid;
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_add(m_ioctx, &peer_uuid,
+ RBD_MIRROR_PEER_DIRECTION_RX_TX,
+ "cluster", "client"));
+ // create mirror images
+ uint64_t features;
+ ASSERT_TRUE(get_features(&features));
+ 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, features,
+ &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_enable2(
+ RBD_MIRROR_IMAGE_MODE_SNAPSHOT));
+ }
+
+ // create snapshots
+ std::list<uint64_t> snap_ids;
+ std::list<librbd::RBD::AioCompletion *> aio_comps;
+ for (auto &image : images) {
+ snap_ids.emplace_back();
+ aio_comps.push_back(new librbd::RBD::AioCompletion(nullptr, nullptr));
+ ASSERT_EQ(0, image.aio_mirror_image_create_snapshot(0, &snap_ids.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();
+
+ // verify
+ for (auto &image : images) {
+ vector<librbd::snap_info_t> snaps;
+ ASSERT_EQ(0, image.snap_list(snaps));
+ ASSERT_EQ(2U, snaps.size());
+ ASSERT_EQ(snaps[1].id, snap_ids.front());
+
+ std::string image_name;
+ ASSERT_EQ(0, image.get_name(&image_name));
+ ASSERT_EQ(0, image.close());
+ ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
+ snap_ids.pop_front();
+ }
+
+ ASSERT_EQ(0, m_rbd.mirror_peer_site_remove(m_ioctx, peer_uuid));
+ ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
+}
diff --git a/src/test/librbd/test_mock_ConfigWatcher.cc b/src/test/librbd/test_mock_ConfigWatcher.cc
new file mode 100644
index 000000000..264e3d2c1
--- /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 000000000..de6ceb64d
--- /dev/null
+++ b/src/test/librbd/test_mock_DeepCopyRequest.cc
@@ -0,0 +1,466 @@
+// -*- 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/AsioEngine.h"
+#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/Handler.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"
+#include <boost/scope_exit.hpp>
+
+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, Handler *handler,
+ 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,
+ librbd::asio::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;
+
+ std::shared_ptr<librbd::AsioEngine> m_asio_engine;
+ librbd::asio::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));
+
+ m_asio_engine = std::make_shared<librbd::AsioEngine>(
+ m_src_image_ctx->md_ctx);
+ m_work_queue = m_asio_engine->get_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(Return(new LambdaContext([](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;
+
+ mock_dst_image_ctx.object_map = nullptr;
+ if (is_feature_enabled(RBD_FEATURE_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);
+ if (mock_dst_image_ctx.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::deep_copy::NoOpHandler 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::deep_copy::NoOpHandler 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::deep_copy::NoOpHandler 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::deep_copy::NoOpHandler 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;
+
+ mock_dst_image_ctx.object_map = nullptr;
+ if (is_feature_enabled(RBD_FEATURE_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);
+ if (mock_dst_image_ctx.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::deep_copy::NoOpHandler 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;
+ if (is_feature_enabled(RBD_FEATURE_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);
+ if (mock_dst_image_ctx.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::deep_copy::NoOpHandler 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::deep_copy::NoOpHandler 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 000000000..6feb54ec6
--- /dev/null
+++ b/src/test/librbd/test_mock_ExclusiveLock.cc
@@ -0,0 +1,831 @@
+// -*- 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 "test/librbd/mock/io/MockImageDispatch.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/ManagedLock.h"
+#include "librbd/exclusive_lock/ImageDispatch.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>
+#include <boost/scope_exit.hpp>
+
+namespace librbd {
+
+namespace {
+
+struct MockExclusiveLockImageCtx : public MockImageCtx {
+ asio::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, AsioEngine& asio_engine,
+ const std::string& oid, librbd::MockImageWatcher *watcher,
+ managed_lock::Mode mode, bool blocklist_on_break_lock,
+ uint32_t blocklist_expire_seconds)
+ {}
+
+ virtual ~ManagedLock() = default;
+
+ mutable ceph::mutex m_lock = ceph::make_mutex("ManagedLock::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 ImageDispatch<MockExclusiveLockImageCtx>
+ : public librbd::io::MockImageDispatch {
+ static ImageDispatch* s_instance;
+ static ImageDispatch* create(MockExclusiveLockImageCtx*) {
+ ceph_assert(s_instance != nullptr);
+ return s_instance;
+ }
+
+ void destroy() {
+ }
+
+ ImageDispatch() {
+ s_instance = this;
+ }
+
+ io::ImageDispatchLayer get_dispatch_layer() const override {
+ return io::IMAGE_DISPATCH_LAYER_EXCLUSIVE_LOCK;
+ }
+
+ MOCK_METHOD3(set_require_lock, void(bool, io::Direction, Context*));
+ MOCK_METHOD1(unset_require_lock, void(io::Direction));
+
+};
+
+ImageDispatch<MockExclusiveLockImageCtx>* ImageDispatch<MockExclusiveLockImageCtx>::s_instance = nullptr;
+
+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,
+ ImageDispatch<MockExclusiveLockImageCtx>* ImageDispatch,
+ 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;
+using ::testing::WithArg;
+
+class TestMockExclusiveLock : public TestMockFixture {
+public:
+ typedef ManagedLock<MockExclusiveLockImageCtx> MockManagedLock;
+ typedef ExclusiveLock<MockExclusiveLockImageCtx> MockExclusiveLock;
+ typedef exclusive_lock::ImageDispatch<MockExclusiveLockImageCtx> MockImageDispatch;
+ 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(MockImageDispatch &mock_image_dispatch,
+ bool init_shutdown, io::Direction direction) {
+ EXPECT_CALL(mock_image_dispatch, set_require_lock(init_shutdown,
+ direction, _))
+ .WillOnce(WithArg<2>(Invoke([](Context* ctx) { ctx->complete(0); })));
+ }
+
+ void expect_set_require_lock(MockExclusiveLockImageCtx &mock_image_ctx,
+ MockImageDispatch &mock_image_dispatch,
+ bool init_shutdown) {
+ if (mock_image_ctx.clone_copy_on_read ||
+ (mock_image_ctx.features & RBD_FEATURE_JOURNALING) != 0 ||
+ is_rbd_pwl_enabled(mock_image_ctx.cct)) {
+ expect_set_require_lock(mock_image_dispatch, init_shutdown,
+ io::DIRECTION_BOTH);
+ } else {
+ expect_set_require_lock(mock_image_dispatch, init_shutdown,
+ io::DIRECTION_WRITE);
+ }
+ }
+
+ void expect_unset_require_lock(MockImageDispatch &mock_image_dispatch) {
+ EXPECT_CALL(mock_image_dispatch, unset_require_lock(io::DIRECTION_BOTH));
+ }
+
+ void expect_register_dispatch(MockExclusiveLockImageCtx &mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, register_dispatch(_));
+ }
+
+ void expect_shut_down_dispatch(MockExclusiveLockImageCtx &mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, shut_down_dispatch(_, _))
+ .WillOnce(WithArg<1>(Invoke([](Context* ctx) {
+ ctx->complete(0);
+ })));
+ }
+
+ 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<asio::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;
+ {
+ std::unique_lock 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;
+ {
+ std::unique_lock 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) {
+ std::shared_lock 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 = new MockExclusiveLock(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&exclusive_lock) {
+ exclusive_lock->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+ expect_set_state_initializing(exclusive_lock);
+ MockImageDispatch mock_image_dispatch;
+ expect_register_dispatch(mock_image_ctx);
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, true);
+ 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_unset_require_lock(mock_image_dispatch);
+ 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_unset_require_lock(mock_image_dispatch);
+ 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_shut_down_dispatch(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 = new MockExclusiveLock(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&exclusive_lock) {
+ exclusive_lock->put();
+ } BOOST_SCOPE_EXIT_END
+
+ expect_set_state_initializing(exclusive_lock);
+ MockImageDispatch mock_image_dispatch;
+ expect_register_dispatch(mock_image_ctx);
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, true);
+ 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 = new MockExclusiveLock(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&exclusive_lock) {
+ exclusive_lock->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+ expect_set_state_initializing(exclusive_lock);
+ MockImageDispatch mock_image_dispatch;
+ expect_register_dispatch(mock_image_ctx);
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, true);
+ 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 = new MockExclusiveLock(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&exclusive_lock) {
+ exclusive_lock->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+ expect_set_state_initializing(exclusive_lock);
+ MockImageDispatch mock_image_dispatch;
+ expect_register_dispatch(mock_image_ctx);
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, true);
+ 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 = new MockExclusiveLock(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&exclusive_lock) {
+ exclusive_lock->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+ expect_set_state_initializing(exclusive_lock);
+ MockImageDispatch mock_image_dispatch;
+ expect_register_dispatch(mock_image_ctx);
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, true);
+ 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 = new MockExclusiveLock(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&exclusive_lock) {
+ exclusive_lock->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+ expect_set_state_initializing(exclusive_lock);
+ MockImageDispatch mock_image_dispatch;
+ expect_register_dispatch(mock_image_ctx);
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, true);
+ 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(-EBLOCKLISTED, when_post_acquire_lock_handler(exclusive_lock,
+ -EBLOCKLISTED));
+}
+
+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 = new MockExclusiveLock(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&exclusive_lock) {
+ exclusive_lock->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+ expect_set_state_initializing(exclusive_lock);
+ MockImageDispatch mock_image_dispatch;
+ expect_register_dispatch(mock_image_ctx);
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, true);
+ 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 = new MockExclusiveLock(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&exclusive_lock) {
+ exclusive_lock->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+ expect_set_state_initializing(exclusive_lock);
+ MockImageDispatch mock_image_dispatch;
+ expect_register_dispatch(mock_image_ctx);
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, true);
+ 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 = new MockExclusiveLock(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&exclusive_lock) {
+ exclusive_lock->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+ expect_set_state_initializing(exclusive_lock);
+ MockImageDispatch mock_image_dispatch;
+ expect_register_dispatch(mock_image_ctx);
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, true);
+ 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_unset_require_lock(mock_image_dispatch);
+ 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_shut_down_dispatch(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 = new MockExclusiveLock(mock_image_ctx);
+ exclusive_lock::MockPolicy mock_exclusive_lock_policy;
+
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&exclusive_lock) {
+ exclusive_lock->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+ expect_set_state_initializing(exclusive_lock);
+ MockImageDispatch mock_image_dispatch;
+ expect_register_dispatch(mock_image_ctx);
+ expect_set_require_lock(mock_image_ctx, mock_image_dispatch, true);
+ 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 000000000..b57c66e89
--- /dev/null
+++ b/src/test/librbd/test_mock_Journal.cc
@@ -0,0 +1,1574 @@
+// -*- 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/ceph_mutex.h"
+#include "common/WorkQueue.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,
+ ceph::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() = default;
+ ~TestMockJournal() override {
+ ceph_assert(m_commit_contexts.empty());
+ }
+
+ ceph::mutex m_lock = ceph::make_mutex("lock");
+ ceph::condition_variable 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<asio::ContextWQ*>(NULL)));
+ }
+
+ void expect_register_dispatch(MockImageCtx& mock_image_ctx,
+ MockObjectDispatch& mock_object_dispatch) {
+ EXPECT_CALL(*mock_image_ctx.io_object_dispatcher,
+ register_dispatch(&mock_object_dispatch));
+ }
+
+ void expect_shut_down_dispatch(MockImageCtx& mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.io_object_dispatcher,
+ shut_down_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<asio::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<asio::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<asio::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<asio::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);
+
+ std::shared_lock 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) {
+ std::shared_lock 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) {
+ std::lock_guard locker{m_lock};
+ m_commit_contexts.push_back(ctx);
+ m_cond.notify_all();
+ }
+
+ void wake_up() {
+ std::lock_guard locker{m_lock};
+ m_cond.notify_all();
+ }
+
+ 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 LambdaContext([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_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_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 = new MockJournal(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&mock_journal) {
+ mock_journal->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+
+ MockObjectDispatch mock_object_dispatch;
+ expect_register_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_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 = new MockJournal(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&mock_journal) {
+ mock_journal->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+
+ MockObjectDispatch mock_object_dispatch;
+ expect_register_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 = new MockJournal(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&mock_journal) {
+ mock_journal->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+
+ MockObjectDispatch mock_object_dispatch;
+ expect_register_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_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 = new MockJournal(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&mock_journal) {
+ mock_journal->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+
+ MockObjectDispatch mock_object_dispatch;
+ expect_register_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_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 = new MockJournal(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&mock_journal) {
+ mock_journal->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+
+ MockObjectDispatch mock_object_dispatch;
+ expect_register_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_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 = new MockJournal(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&mock_journal) {
+ mock_journal->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+
+ MockObjectDispatch mock_object_dispatch;
+ expect_register_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_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 = new MockJournal(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&mock_journal) {
+ mock_journal->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+ MockObjectDispatch mock_object_dispatch;
+ expect_register_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
+ {
+ std::unique_lock locker{m_lock};
+ m_cond.wait(locker, [this] { return !m_commit_contexts.empty(); });
+ }
+ 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_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 = new MockJournal(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&mock_journal) {
+ mock_journal->put();
+ } BOOST_SCOPE_EXIT_END
+
+ InSequence seq;
+
+ MockObjectDispatch mock_object_dispatch;
+ expect_register_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
+ std::unique_lock locker{m_lock};
+ m_cond.wait(locker, [&] {return on_flush != nullptr;});
+ }
+
+ {
+ // wait for the on_safe process callback
+ std::unique_lock locker{m_lock};
+ m_cond.wait(locker, [this] {return !m_commit_contexts.empty();});
+ }
+ 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_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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ ::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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ 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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ ::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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ ::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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ ::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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ ::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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ 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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ 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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ 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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ InSequence seq;
+ std::shared_lock image_locker{mock_image_ctx.image_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 = new MockJournal(mock_image_ctx);
+
+ BOOST_SCOPE_EXIT(&mock_journal) {
+ mock_journal->put();
+ } BOOST_SCOPE_EXIT_END
+
+ 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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ 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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ 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 = new MockJournal(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);
+ mock_journal->put();
+ };
+
+ 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 000000000..3e6b15f61
--- /dev/null
+++ b/src/test/librbd/test_mock_ManagedLock.cc
@@ -0,0 +1,694 @@
+// -*- 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, AsioEngine& asio_engine,
+ const std::string& oid, librbd::MockImageWatcher *watcher,
+ managed_lock::Mode mode, bool blocklist_on_break_lock,
+ uint32_t blocklist_expire_seconds)
+ : ManagedLock<MockManagedLockImageCtx>(ioctx, asio_engine, 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,
+ 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,
+ AsioEngine& asio_engine,
+ const std::string& oid,
+ const std::string& cookie,
+ bool exclusive, bool blocklist_on_break_lock,
+ uint32_t blocklist_expire_seconds,
+ Context *on_finish) {
+ return BaseRequest::create(ioctx, watcher, 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, 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,
+ asio::ContextWQ *work_queue,
+ const std::string& oid,
+ const std::string& cookie, Context *on_finish) {
+ return BaseRequest::create(ioctx, watcher, 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,
+ AsioEngine& asio_engine,
+ const std::string& oid, const Locker &locker,
+ bool exclusive, bool blocklist_locker,
+ uint32_t blocklist_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,
+ asio::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(asio::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,
+ asio::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, (asio::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->asio_engine,
+ 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->asio_engine,
+ 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->asio_engine,
+ 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->asio_engine,
+ 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->asio_engine,
+ 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, AcquireLockBlocklist) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockManagedLockImageCtx mock_image_ctx(*ictx);
+ MockManagedLock managed_lock(ictx->md_ctx, *ictx->asio_engine,
+ ictx->header_oid, mock_image_ctx.image_watcher,
+ librbd::managed_lock::EXCLUSIVE, true, 0);
+ InSequence seq;
+
+ // will abort after seeing blocklist error (avoid infinite request loop)
+ MockAcquireRequest request_lock_acquire;
+ expect_acquire_lock(*mock_image_ctx.image_watcher, ictx->op_work_queue, request_lock_acquire, -EBLOCKLISTED);
+ ASSERT_EQ(-EBLOCKLISTED, 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->asio_engine,
+ 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, ReleaseLockBlocklist) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockManagedLockImageCtx mock_image_ctx(*ictx);
+ MockMockManagedLock managed_lock(ictx->md_ctx, *ictx->asio_engine,
+ 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, -EBLOCKLISTED);
+ expect_post_release_lock_handler(managed_lock, false, -EBLOCKLISTED, -EBLOCKLISTED);
+ ASSERT_EQ(-EBLOCKLISTED, 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->asio_engine,
+ 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->asio_engine,
+ 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->asio_engine,
+ 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, AttemptReacquireBlocklistedLock) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockManagedLockImageCtx mock_image_ctx(*ictx);
+ MockManagedLock managed_lock(ictx->md_ctx, *ictx->asio_engine,
+ 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, ReacquireBlocklistedLock) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockManagedLockImageCtx mock_image_ctx(*ictx);
+ MockManagedLock managed_lock(ictx->md_ctx, *ictx->asio_engine,
+ 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->asio_engine,
+ 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->asio_engine,
+ 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->asio_engine,
+ 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 000000000..39e29172c
--- /dev/null
+++ b/src/test/librbd/test_mock_ObjectMap.cc
@@ -0,0 +1,285 @@
+// -*- 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"
+#include <boost/scope_exit.hpp>
+
+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::shared_mutex*,
+ 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::shared_mutex*,
+ 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 = new MockObjectMap(mock_image_ctx, CEPH_NOSNAP);
+ BOOST_SCOPE_EXIT(&mock_object_map) {
+ mock_object_map->put();
+ } BOOST_SCOPE_EXIT_END
+
+ C_SaferCond open_ctx;
+ mock_object_map->open(&open_ctx);
+ ASSERT_EQ(0, open_ctx.wait());
+
+ C_SaferCond update_ctx1;
+ C_SaferCond update_ctx2;
+ {
+ std::shared_lock image_locker{mock_image_ctx.image_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 = new MockObjectMap(mock_image_ctx, CEPH_NOSNAP);
+ BOOST_SCOPE_EXIT(&mock_object_map) {
+ mock_object_map->put();
+ } BOOST_SCOPE_EXIT_END
+
+ 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;
+ {
+ std::shared_lock image_locker{mock_image_ctx.image_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 000000000..d7f3c679e
--- /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 000000000..97f775740
--- /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/ceph_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, asio::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::DoAll;
+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() = default;
+
+ 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 LambdaContext([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 LambdaContext([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() {
+ std::lock_guard locker{m_lock};
+ ++m_watch_count;
+ m_cond.notify_all();
+ }
+
+ bool wait_for_watch(MockImageCtx &mock_image_ctx, size_t count) {
+ std::unique_lock locker{m_lock};
+ while (m_watch_count < count) {
+ if (m_cond.wait_for(locker, 10s) == std::cv_status::timeout) {
+ return false;
+ }
+ }
+ m_watch_count -= count;
+ return true;
+ }
+
+ ceph::mutex m_lock = ceph::make_mutex("TestMockWatcher::m_lock");
+ ceph::condition_variable 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, ReregisterWatchBlocklist) {
+ 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, -EBLOCKLISTED);
+
+ 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, -EBLOCKLISTED);
+
+ // wait for recovery unwatch/watch
+ ASSERT_TRUE(wait_for_watch(mock_image_ctx, 2));
+ ASSERT_TRUE(mock_image_watcher.is_blocklisted());
+
+ 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, -EBLOCKLISTED,
+ [&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, -EBLOCKLISTED);
+
+ 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 000000000..d2a6837c6
--- /dev/null
+++ b/src/test/librbd/test_mock_fixture.cc
@@ -0,0 +1,134 @@
+// -*- 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());
+ if (ictx.test_features(RBD_FEATURE_DIRTY_CACHE)) {
+ EXPECT_CALL(get_mock_io_ctx(ictx.md_ctx),
+ exec(ictx.header_oid, _, StrEq("rbd"), StrEq("set_features"), _, _, _, _))
+ .WillOnce(DoDefault());
+ EXPECT_CALL(get_mock_io_ctx(ictx.md_ctx),
+ exec(ictx.header_oid, _, StrEq("rbd"), StrEq("metadata_set"), _, _, _, _))
+ .WillOnce(DoDefault());
+ EXPECT_CALL(get_mock_io_ctx(ictx.md_ctx),
+ exec(ictx.header_oid, _, StrEq("rbd"), StrEq("metadata_remove"), _, _, _, _))
+ .WillOnce(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 000000000..4e96d955f
--- /dev/null
+++ b/src/test/librbd/test_mock_fixture.h
@@ -0,0 +1,89 @@
+// -*- 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 "librbd/asio/ContextWQ.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) {
+ librbd::asio::ContextWQ *context_wq = reinterpret_cast<
+ librbd::asio::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 000000000..4e929d74f
--- /dev/null
+++ b/src/test/librbd/test_notify.py
@@ -0,0 +1,183 @@
+#!/usr/bin/python3
+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()) == [])
+
+ if 'RBD_DISABLE_UPDATE_FEATURES' not in os.environ:
+ 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)
+ else:
+ print("skipping update_features")
+
+ 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 000000000..bc9d2543d
--- /dev/null
+++ b/src/test/librbd/test_support.cc
@@ -0,0 +1,137 @@
+// -*- 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 "common/ceph_context.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";
+}
+
+bool is_rbd_pwl_enabled(ceph::common::CephContext *cct) {
+#if defined(WITH_RBD_RWL) || defined(WITH_RBD_SSD_CACHE)
+ auto value = cct->_conf.get_val<std::string>("rbd_persistent_cache_mode");
+ return value == "disabled" ? false : true;
+#else
+ return false;
+#endif
+}
diff --git a/src/test/librbd/test_support.h b/src/test/librbd/test_support.h
new file mode 100644
index 000000000..2d2de175b
--- /dev/null
+++ b/src/test/librbd/test_support.h
@@ -0,0 +1,40 @@
+// -*- 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);
+
+bool is_rbd_pwl_enabled(ceph::common::CephContext *ctx);
+
+#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 000000000..1a35c62ae
--- /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 000000000..51d6123de
--- /dev/null
+++ b/src/test/librbd/trash/test_mock_RemoveRequest.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/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);
+ }));
+ }
+
+ 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 000000000..9e7c5e023
--- /dev/null
+++ b/src/test/librbd/watcher/test_mock_RewatchRequest.cc
@@ -0,0 +1,227 @@
+// -*- 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() = default;
+
+ 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 LambdaContext([&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 LambdaContext([&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();
+ }
+ };
+
+ ceph::shared_mutex m_watch_lock = ceph::make_shared_mutex("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);
+ {
+ std::unique_lock 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);
+ {
+ std::unique_lock watch_locker{m_watch_lock};
+ req->send();
+ }
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_EQ(234U, m_watch_handle);
+}
+
+TEST_F(TestMockWatcherRewatchRequest, WatchBlocklist) {
+ 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, -EBLOCKLISTED);
+
+ 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);
+ {
+ std::unique_lock watch_locker{m_watch_lock};
+ req->send();
+ }
+ ASSERT_EQ(-EBLOCKLISTED, 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);
+ {
+ std::unique_lock 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);
+ {
+ std::unique_lock 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);
+ {
+ std::unique_lock watch_locker{m_watch_lock};
+ req->send();
+ }
+
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_EQ(234U, m_watch_handle);
+}
+
+} // namespace watcher
+} // namespace librbd