diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/test/librbd | |
parent | Initial commit. (diff) | |
download | ceph-upstream/16.2.11+ds.tar.xz ceph-upstream/16.2.11+ds.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
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, + ©_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> ¤t_state, + bool updated, int ret_val) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, + aio_update(CEPH_NOSNAP, start_object, end_object, state, + current_state, _, false, _)) + .WillOnce(WithArg<7>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) { + if (updated) { + mock_image_ctx.op_work_queue->queue(ctx, ret_val); + } + return updated; + }))); + } + } + + void expect_assert_exists(MockTestImageCtx &mock_image_ctx, int r) { + 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, ®istered_clients, &cond); + ASSERT_EQ(0, cond.wait()); + std::set<cls::journal::Client>::const_iterator c; + for (c = registered_clients.begin(); c != registered_clients.end(); ++c) { + if (c->id == client_id) { + break; + } + } + if (c == registered_clients.end() || + c->commit_position.object_positions.empty()) { + *tag = 0; + *entry = -1; + } else { + const cls::journal::ObjectPosition &object_position = + *c->commit_position.object_positions.begin(); + *tag = object_position.tag_tid; + *entry = object_position.entry_tid; + } + + C_SaferCond open_cond; + ictx->journal = new librbd::Journal<>(*ictx); + ictx->journal->open(&open_cond); + ASSERT_EQ(0, open_cond.wait()); + } +}; + +TEST_F(TestJournalReplay, AioDiscardEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + // write to the image w/o using the journal + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->features &= ~RBD_FEATURE_JOURNALING; + + std::string payload(4096, '1'); + bufferlist payload_bl; + payload_bl.append(payload); + auto aio_comp = new librbd::io::AioCompletion(); + 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, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + // replay several envents and check the commit position + inject_into_journal(ictx, + librbd::journal::AioDiscardEvent( + 0, payload.size(), ictx->discard_granularity_bytes)); + inject_into_journal(ictx, + librbd::journal::AioDiscardEvent( + 0, payload.size(), ictx->discard_granularity_bytes)); + close_image(ictx); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 2, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + aio_comp = new librbd::io::AioCompletion(); + 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, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + // replay several events and check the commit position + inject_into_journal(ictx, + librbd::journal::AioWriteEvent(0, payload.size(), payload_bl)); + inject_into_journal(ictx, + librbd::journal::AioWriteEvent(0, payload.size(), payload_bl)); + close_image(ictx); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 2, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + aio_comp = new librbd::io::AioCompletion(); + 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, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + // replay several events and check the commit position + inject_into_journal(ictx, librbd::journal::AioFlushEvent()); + inject_into_journal(ictx, librbd::journal::AioFlushEvent()); + close_image(ictx); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 2, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + auto aio_comp = new librbd::io::AioCompletion(); + 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, ¤t_tag, ¤t_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, ¤t_tag, ¤t_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, ¤t_tag, ¤t_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, ¤t_tag, ¤t_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, ¤t_tag, ¤t_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, ¤t_tag, ¤t_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, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + librbd::RBD rbd; + ASSERT_EQ(0, rbd.rename(m_ioctx, new_image_name.c_str(), m_image_name.c_str())); +} + +TEST_F(TestJournalReplay, Resize) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, librbd::journal::ResizeEvent(1, 16)); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + librbd::NoOpProgressContext no_op_progress; + ASSERT_EQ(0, ictx->operations->resize(0, true, no_op_progress)); +} + +TEST_F(TestJournalReplay, Flatten) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + 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, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + ASSERT_EQ(0, ictx->operations->snap_unprotect(cls::rbd::UserSnapshotNamespace(), + "snap")); + + // verify lock ordering constraints + librbd::NoOpProgressContext no_op; + ASSERT_EQ(-EINVAL, ictx2->operations->flatten(no_op)); +} + +TEST_F(TestJournalReplay, UpdateFeatures) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + uint64_t features = RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF; + bool enabled = !ictx->test_features(features); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject update_features op into journal + inject_into_journal(ictx, librbd::journal::UpdateFeaturesEvent(1, features, + enabled)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + ASSERT_EQ(enabled, ictx->test_features(features)); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->update_features(features, !enabled)); +} + +TEST_F(TestJournalReplay, MetadataSet) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject metadata_set op into journal + inject_into_journal(ictx, librbd::journal::MetadataSetEvent( + 1, "conf_rbd_mirroring_replay_delay", "9876")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + ASSERT_EQ(9876U, ictx->mirroring_replay_delay); + + std::string value; + ASSERT_EQ(0, librbd::metadata_get(ictx, "conf_rbd_mirroring_replay_delay", + &value)); + ASSERT_EQ("9876", value); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->metadata_set("key2", "value")); +} + +TEST_F(TestJournalReplay, MetadataRemove) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + ASSERT_EQ(0, ictx->operations->metadata_set( + "conf_rbd_mirroring_replay_delay", "9876")); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject metadata_remove op into journal + inject_into_journal(ictx, librbd::journal::MetadataRemoveEvent( + 1, "conf_rbd_mirroring_replay_delay")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + ASSERT_EQ(0U, ictx->mirroring_replay_delay); + + std::string value; + ASSERT_EQ(-ENOENT, + librbd::metadata_get(ictx, "conf_rbd_mirroring_replay_delay", + &value)); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->metadata_set("key", "value")); + ASSERT_EQ(0, ictx->operations->metadata_remove("key")); +} + +TEST_F(TestJournalReplay, ObjectPosition) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + std::string payload(4096, '1'); + bufferlist payload_bl; + payload_bl.append(payload); + auto aio_comp = new librbd::io::AioCompletion(); + 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, ¤t_tag, ¤t_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, ¤t_tag, ¤t_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 ×tamp_lock; + ceph::mutex &async_ops_lock; + ceph::mutex ©up_list_lock; + + uint8_t order; + uint64_t size; + uint64_t features; + uint64_t flags; + uint64_t op_features; + bool operations_disabled; + uint64_t stripe_unit; + uint64_t stripe_count; + std::string object_prefix; + std::string header_oid; + std::string id; + std::string name; + ParentImageInfo parent_md; + MigrationInfo migration_info; + char *format_string; + cls::rbd::GroupSpec group_spec; + + file_layout_t layout; + + xlist<operation::ResizeRequest<MockImageCtx>*> resize_reqs; + xlist<AsyncRequest<MockImageCtx>*> async_requests; + std::list<Context*> async_requests_waiters; + + std::map<uint64_t, io::CopyupRequest<MockImageCtx>*> copyup_list; + + io::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> ¤t_state, + const ZTracer::Trace &parent_trace, bool ignore_enoent, + T *callback_object) { + return aio_update<T, MF>(snap_id, start_object_no, start_object_no + 1, + new_state, current_state, parent_trace, + ignore_enoent, callback_object); + } + + template <typename T, void(T::*MF)(int) = &T::complete> + bool aio_update(uint64_t snap_id, uint64_t start_object_no, + uint64_t end_object_no, uint8_t new_state, + const boost::optional<uint8_t> ¤t_state, + const ZTracer::Trace &parent_trace, bool ignore_enoent, + T *callback_object) { + auto ctx = util::create_context_callback<T, MF>(callback_object); + bool updated = aio_update(snap_id, start_object_no, end_object_no, + new_state, current_state, parent_trace, + ignore_enoent, ctx); + if (!updated) { + delete ctx; + } + return updated; + } + MOCK_METHOD8(aio_update, bool(uint64_t snap_id, uint64_t start_object_no, + uint64_t end_object_no, uint8_t new_state, + const boost::optional<uint8_t> ¤t_state, + const ZTracer::Trace &parent_trace, + bool ignore_enoent, Context *on_finish)); + + MOCK_METHOD2(snapshot_add, void(uint64_t snap_id, Context *on_finish)); + MOCK_METHOD2(snapshot_remove, void(uint64_t snap_id, Context *on_finish)); + MOCK_METHOD2(rollback, void(uint64_t snap_id, Context *on_finish)); + + MOCK_CONST_METHOD1(object_may_exist, bool(uint64_t)); + +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_OBJECT_MAP_H diff --git a/src/test/librbd/mock/MockOperations.h b/src/test/librbd/mock/MockOperations.h new file mode 100644 index 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 ℑ + 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, ¬ify_ctx); + ASSERT_EQ(-EEXIST, notify_ctx.wait()); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_SNAP_CREATE; + ASSERT_EQ(expected_notify_ops, m_notifies); +} + +TEST_F(TestImageWatcher, NotifySnapRename) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, 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", ¬ify_ctx); + ASSERT_EQ(-EEXIST, notify_ctx.wait()); + + NotifyOps expected_notify_ops; + expected_notify_ops += NOTIFY_OP_SNAP_RENAME; + ASSERT_EQ(expected_notify_ops, m_notifies); +} + +TEST_F(TestImageWatcher, NotifySnapRemove) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, register_image_watch(*ictx)); + ASSERT_EQ(0, lock_image(*ictx, 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, ×tamp)); + ASSERT_LT(0, timestamp.tv_sec); + + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, GetCreateTimestampPP) +{ + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), 0, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + struct timespec timestamp; + ASSERT_EQ(0, image.get_create_timestamp(×tamp)); + ASSERT_LT(0, timestamp.tv_sec); +} + +TEST_F(TestLibRBD, OpenAio) +{ + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + + rbd_image_info_t info; + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + + rbd_completion_t open_comp; + ASSERT_EQ(0, rbd_aio_create_completion(NULL, NULL, &open_comp)); + ASSERT_EQ(0, rbd_aio_open(ioctx, name.c_str(), &image, NULL, open_comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(open_comp)); + ASSERT_EQ(1, rbd_aio_is_complete(open_comp)); + ASSERT_EQ(0, rbd_aio_get_return_value(open_comp)); + rbd_aio_release(open_comp); + + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + printf("image has size %llu and order %d\n", (unsigned long long) info.size, info.order); + ASSERT_EQ(info.size, size); + ASSERT_EQ(info.order, order); + + rbd_completion_t close_comp; + ASSERT_EQ(0, rbd_aio_create_completion(NULL, NULL, &close_comp)); + ASSERT_EQ(0, rbd_aio_close(image, close_comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(close_comp)); + ASSERT_EQ(1, rbd_aio_is_complete(close_comp)); + ASSERT_EQ(0, rbd_aio_get_return_value(close_comp)); + rbd_aio_release(close_comp); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, OpenAioFail) +{ + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx)); + + std::string name = get_temp_image_name(); + rbd_image_t image; + rbd_completion_t open_comp; + ASSERT_EQ(0, rbd_aio_create_completion(NULL, NULL, &open_comp)); + ASSERT_EQ(0, rbd_aio_open(ioctx, name.c_str(), &image, NULL, open_comp)); + ASSERT_EQ(0, rbd_aio_wait_for_complete(open_comp)); + ASSERT_EQ(1, rbd_aio_is_complete(open_comp)); + ASSERT_EQ(-ENOENT, rbd_aio_get_return_value(open_comp)); + rbd_aio_release(open_comp); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, OpenAioPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::image_info_t info; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::RBD::AioCompletion *open_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, rbd.aio_open(ioctx, image, name.c_str(), NULL, open_comp)); + ASSERT_EQ(0, open_comp->wait_for_complete()); + ASSERT_EQ(1, open_comp->is_complete()); + ASSERT_EQ(0, open_comp->get_return_value()); + open_comp->release(); + + ASSERT_EQ(0, image.stat(info, sizeof(info))); + ASSERT_EQ(info.size, size); + ASSERT_EQ(info.order, order); + + // reopen + open_comp = new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, rbd.aio_open(ioctx, image, name.c_str(), NULL, open_comp)); + ASSERT_EQ(0, open_comp->wait_for_complete()); + ASSERT_EQ(1, open_comp->is_complete()); + ASSERT_EQ(0, open_comp->get_return_value()); + open_comp->release(); + + // close + librbd::RBD::AioCompletion *close_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, image.aio_close(close_comp)); + ASSERT_EQ(0, close_comp->wait_for_complete()); + ASSERT_EQ(1, close_comp->is_complete()); + ASSERT_EQ(0, close_comp->get_return_value()); + close_comp->release(); + + // close closed image + close_comp = new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(-EINVAL, image.aio_close(close_comp)); + close_comp->release(); + + ioctx.close(); +} + +TEST_F(TestLibRBD, OpenAioFailPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + std::string name = get_temp_image_name(); + + librbd::RBD::AioCompletion *open_comp = + new librbd::RBD::AioCompletion(NULL, NULL); + ASSERT_EQ(0, rbd.aio_open(ioctx, image, name.c_str(), NULL, open_comp)); + ASSERT_EQ(0, open_comp->wait_for_complete()); + ASSERT_EQ(1, open_comp->is_complete()); + ASSERT_EQ(-ENOENT, open_comp->get_return_value()); + open_comp->release(); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, ResizeAndStat) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_info_t info; + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_resize(image, size * 4)); + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + ASSERT_EQ(info.size, size * 4); + + ASSERT_EQ(0, rbd_resize(image, size / 2)); + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + ASSERT_EQ(info.size, size / 2); + + // downsizing without allowing shrink should fail + // and image size should not change + ASSERT_EQ(-EINVAL, rbd_resize2(image, size / 4, false, NULL, NULL)); + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + ASSERT_EQ(info.size, size / 2); + + ASSERT_EQ(0, rbd_resize2(image, size / 4, true, NULL, NULL)); + ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info))); + ASSERT_EQ(info.size, size / 4); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, ResizeAndStatPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::image_info_t info; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + ASSERT_EQ(0, image.resize(size * 4)); + ASSERT_EQ(0, image.stat(info, sizeof(info))); + ASSERT_EQ(info.size, size * 4); + + ASSERT_EQ(0, image.resize(size / 2)); + ASSERT_EQ(0, image.stat(info, sizeof(info))); + ASSERT_EQ(info.size, size / 2); + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, UpdateWatchAndResize) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + struct Watcher { + rbd_image_t &m_image; + 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, ×tamp)); + EXPECT_LT(0, timestamp.tv_sec); + return 0; +} + +TEST_F(TestLibRBD, TestGetSnapShotTimeStamp) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int num_snaps, max_size = 10; + rbd_snap_info_t snaps[max_size]; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_snap_create(image, "snap1")); + num_snaps = rbd_snap_list(image, snaps, &max_size); + ASSERT_EQ(1, num_snaps); + ASSERT_EQ(0, test_get_snapshot_timestamp(image, snaps[0].id)); + free((void *)snaps[0].name); + + ASSERT_EQ(0, rbd_snap_create(image, "snap2")); + num_snaps = rbd_snap_list(image, snaps, &max_size); + ASSERT_EQ(2, num_snaps); + ASSERT_EQ(0, test_get_snapshot_timestamp(image, snaps[0].id)); + ASSERT_EQ(0, test_get_snapshot_timestamp(image, snaps[1].id)); + free((void *)snaps[0].name); + free((void *)snaps[1].name); + + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + + +int test_ls_snaps(librbd::Image& image, size_t num_expected, ...) +{ + int r; + size_t i, j; + va_list ap; + vector<librbd::snap_info_t> snaps; + r = image.snap_list(snaps); + EXPECT_TRUE(r >= 0); + cout << "num snaps is: " << snaps.size() << std::endl + << "expected: " << num_expected << std::endl; + + for (i = 0; i < snaps.size(); i++) { + cout << "snap: " << snaps[i].name << std::endl; + } + + va_start(ap, num_expected); + for (i = num_expected; i > 0; i--) { + char *expected = va_arg(ap, char *); + uint64_t expected_size = va_arg(ap, uint64_t); + int found = 0; + for (j = 0; j < snaps.size(); j++) { + if (snaps[j].name == "") + continue; + if (strcmp(snaps[j].name.c_str(), expected) == 0) { + cout << "found " << snaps[j].name << " with size " << snaps[j].size + << std::endl; + EXPECT_EQ(expected_size, snaps[j].size); + snaps[j].name = ""; + found = 1; + break; + } + } + EXPECT_TRUE(found); + } + va_end(ap); + + for (i = 0; i < snaps.size(); i++) { + EXPECT_EQ("", snaps[i].name); + } + + return snaps.size(); +} + +TEST_F(TestLibRBD, TestCreateLsDeleteSnapPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + uint64_t size2 = 4 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + bool exists; + ASSERT_EQ(0, image.snap_exists2("snap1", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ(0, image.snap_exists2("snap1", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(1, test_ls_snaps(image, 1, "snap1", size)); + ASSERT_EQ(0, image.resize(size2)); + ASSERT_EQ(0, image.snap_exists2("snap2", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, image.snap_create("snap2")); + ASSERT_EQ(0, image.snap_exists2("snap2", &exists)); + ASSERT_TRUE(exists); + ASSERT_EQ(2, test_ls_snaps(image, 2, "snap1", size, "snap2", size2)); + ASSERT_EQ(0, image.snap_remove("snap1")); + ASSERT_EQ(0, image.snap_exists2("snap1", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(1, test_ls_snaps(image, 1, "snap2", size2)); + ASSERT_EQ(0, image.snap_remove("snap2")); + ASSERT_EQ(0, image.snap_exists2("snap2", &exists)); + ASSERT_FALSE(exists); + ASSERT_EQ(0, test_ls_snaps(image, 0)); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, 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, ×tamp)); + ASSERT_LT(0, timestamp.tv_sec); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, TestGetModifyTimestamp) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + struct timespec timestamp; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + ASSERT_EQ(0, rbd_get_modify_timestamp(image, ×tamp)); + ASSERT_LT(0, timestamp.tv_sec); + + ASSERT_PASSED(validate_object_map, image); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, ZeroOverlapFlatten) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image parent_image; + std::string name = get_temp_image_name(); + + uint64_t size = 1; + int order = 0; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, parent_image, name.c_str(), NULL)); + + uint64_t features; + ASSERT_EQ(0, parent_image.features(&features)); + + ASSERT_EQ(0, parent_image.snap_create("snap")); + ASSERT_EQ(0, parent_image.snap_protect("snap")); + + std::string clone_name = this->get_temp_image_name(); + ASSERT_EQ(0, rbd.clone(ioctx, name.c_str(), "snap", ioctx, clone_name.c_str(), + features, &order)); + + librbd::Image clone_image; + ASSERT_EQ(0, rbd.open(ioctx, clone_image, clone_name.c_str(), NULL)); + ASSERT_EQ(0, clone_image.resize(0)); + ASSERT_EQ(0, clone_image.flatten()); +} + +TEST_F(TestLibRBD, PoolMetadata) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + char keys[1024]; + char vals[1024]; + size_t keys_len = sizeof(keys); + size_t vals_len = sizeof(vals); + + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + + ASSERT_EQ(0, rbd_pool_metadata_list(ioctx, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(0U, keys_len); + ASSERT_EQ(0U, vals_len); + + char value[1024]; + size_t value_len = sizeof(value); + memset_rand(value, value_len); + + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "key1", "value1")); + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "key2", "value2")); + ASSERT_EQ(0, rbd_pool_metadata_get(ioctx, "key1", value, &value_len)); + ASSERT_STREQ(value, "value1"); + value_len = 1; + ASSERT_EQ(-ERANGE, rbd_pool_metadata_get(ioctx, "key1", value, &value_len)); + ASSERT_EQ(value_len, strlen("value1") + 1); + + ASSERT_EQ(-ERANGE, rbd_pool_metadata_list(ioctx, "", 0, keys, &keys_len, vals, + &vals_len)); + keys_len = sizeof(keys); + vals_len = sizeof(vals); + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_pool_metadata_list(ioctx, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1 + strlen("key2") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1 + strlen("value2") + 1); + ASSERT_STREQ(keys, "key1"); + ASSERT_STREQ(keys + strlen(keys) + 1, "key2"); + ASSERT_STREQ(vals, "value1"); + ASSERT_STREQ(vals + strlen(vals) + 1, "value2"); + + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "key1")); + ASSERT_EQ(-ENOENT, rbd_pool_metadata_remove(ioctx, "key3")); + value_len = sizeof(value); + ASSERT_EQ(-ENOENT, rbd_pool_metadata_get(ioctx, "key3", value, &value_len)); + ASSERT_EQ(0, rbd_pool_metadata_list(ioctx, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key2") + 1); + ASSERT_EQ(vals_len, strlen("value2") + 1); + ASSERT_STREQ(keys, "key2"); + ASSERT_STREQ(vals, "value2"); + + // test config setting + ASSERT_EQ(-EINVAL, rbd_pool_metadata_set(ioctx, "conf_UNKNOWN", "false")); + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "conf_rbd_cache", "false")); + ASSERT_EQ(-EINVAL, rbd_pool_metadata_set(ioctx, "conf_rbd_cache", "INVALID")); + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "conf_rbd_cache")); + + // test short buffer cases + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "key1", "value1")); + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "key3", "value3")); + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "key4", "value4")); + + keys_len = strlen("key1") + 1; + vals_len = strlen("value1") + 1; + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_pool_metadata_list(ioctx, "", 1, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1); + ASSERT_STREQ(keys, "key1"); + ASSERT_STREQ(vals, "value1"); + + ASSERT_EQ(-ERANGE, rbd_pool_metadata_list(ioctx, "", 2, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1 + strlen("key2") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1 + strlen("value2") + 1); + + ASSERT_EQ(-ERANGE, rbd_pool_metadata_list(ioctx, "", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key1") + 1 + strlen("key2") + 1 + strlen("key3") + + 1 + strlen("key4") + 1); + ASSERT_EQ(vals_len, strlen("value1") + 1 + strlen("value2") + 1 + + strlen("value3") + 1 + strlen("value4") + 1); + + // test `start` param + keys_len = sizeof(keys); + vals_len = sizeof(vals); + memset_rand(keys, keys_len); + memset_rand(vals, vals_len); + ASSERT_EQ(0, rbd_pool_metadata_list(ioctx, "key2", 0, keys, &keys_len, vals, + &vals_len)); + ASSERT_EQ(keys_len, strlen("key3") + 1 + strlen("key4") + 1); + ASSERT_EQ(vals_len, strlen("value3") + 1 + strlen("value4") + 1); + ASSERT_STREQ(keys, "key3"); + ASSERT_STREQ(vals, "value3"); + + //cleanup + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "key1")); + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "key2")); + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "key3")); + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "key4")); + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, PoolMetadataPP) +{ + REQUIRE_FORMAT_V2(); + + librbd::RBD rbd; + string value; + map<string, bufferlist> pairs; + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + ASSERT_EQ(0, rbd.pool_metadata_list(ioctx, "", 0, &pairs)); + ASSERT_TRUE(pairs.empty()); + + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "key1", "value1")); + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "key2", "value2")); + ASSERT_EQ(0, rbd.pool_metadata_get(ioctx, "key1", &value)); + ASSERT_EQ(value, "value1"); + ASSERT_EQ(0, rbd.pool_metadata_list(ioctx, "", 0, &pairs)); + ASSERT_EQ(2U, pairs.size()); + ASSERT_EQ(0, strncmp("value1", pairs["key1"].c_str(), 6)); + ASSERT_EQ(0, strncmp("value2", pairs["key2"].c_str(), 6)); + + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "key1")); + ASSERT_EQ(-ENOENT, rbd.pool_metadata_remove(ioctx, "key3")); + ASSERT_EQ(-ENOENT, rbd.pool_metadata_get(ioctx, "key3", &value)); + pairs.clear(); + ASSERT_EQ(0, rbd.pool_metadata_list(ioctx, "", 0, &pairs)); + ASSERT_EQ(1U, pairs.size()); + ASSERT_EQ(0, strncmp("value2", pairs["key2"].c_str(), 6)); + + // test `start` param + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "key1", "value1")); + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "key3", "value3")); + + pairs.clear(); + ASSERT_EQ(0, rbd.pool_metadata_list(ioctx, "key2", 0, &pairs)); + ASSERT_EQ(1U, pairs.size()); + ASSERT_EQ(0, strncmp("value3", pairs["key3"].c_str(), 6)); + + // test config setting + ASSERT_EQ(-EINVAL, rbd.pool_metadata_set(ioctx, "conf_UNKNOWN", "false")); + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "conf_rbd_cache", "false")); + ASSERT_EQ(-EINVAL, rbd.pool_metadata_set(ioctx, "conf_rbd_cache", "INVALID")); + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "conf_rbd_cache")); + + // cleanup + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "key1")); + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "key2")); + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "key3")); +} + +TEST_F(TestLibRBD, Config) +{ + REQUIRE_FORMAT_V2(); + + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "conf_rbd_cache", "false")); + + rbd_config_option_t options[1024]; + int max_options = 0; + ASSERT_EQ(-ERANGE, rbd_config_pool_list(ioctx, options, &max_options)); + ASSERT_EQ(0, rbd_config_pool_list(ioctx, options, &max_options)); + ASSERT_GT(max_options, 0); + ASSERT_LT(max_options, 1024); + for (int i = 0; i < max_options; i++) { + if (options[i].name == std::string("rbd_cache")) { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_POOL); + ASSERT_STREQ("false", options[i].value); + } else { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG); + } + } + rbd_config_pool_list_cleanup(options, max_options); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + ASSERT_EQ(0, rbd_config_image_list(image, options, &max_options)); + for (int i = 0; i < max_options; i++) { + if (options[i].name == std::string("rbd_cache")) { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_POOL); + ASSERT_STREQ("false", options[i].value); + } else { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG); + } + } + rbd_config_image_list_cleanup(options, max_options); + + ASSERT_EQ(0, rbd_metadata_set(image, "conf_rbd_cache", "true")); + + ASSERT_EQ(0, rbd_config_image_list(image, options, &max_options)); + for (int i = 0; i < max_options; i++) { + if (options[i].name == std::string("rbd_cache")) { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_IMAGE); + ASSERT_STREQ("true", options[i].value); + } else { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG); + } + } + rbd_config_image_list_cleanup(options, max_options); + + ASSERT_EQ(0, rbd_metadata_remove(image, "conf_rbd_cache")); + + ASSERT_EQ(0, rbd_config_image_list(image, options, &max_options)); + for (int i = 0; i < max_options; i++) { + if (options[i].name == std::string("rbd_cache")) { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_POOL); + ASSERT_STREQ("false", options[i].value); + } else { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG); + } + } + rbd_config_image_list_cleanup(options, max_options); + + ASSERT_EQ(0, rbd_close(image)); + + ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "conf_rbd_cache")); + + ASSERT_EQ(-ERANGE, rbd_config_pool_list(ioctx, options, &max_options)); + ASSERT_EQ(0, rbd_config_pool_list(ioctx, options, &max_options)); + for (int i = 0; i < max_options; i++) { + ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG); + } + rbd_config_pool_list_cleanup(options, max_options); + + rados_ioctx_destroy(ioctx); +} + +TEST_F(TestLibRBD, ConfigPP) +{ + REQUIRE_FORMAT_V2(); + + librbd::RBD rbd; + string value; + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "conf_rbd_cache", "false")); + + std::vector<librbd::config_option_t> options; + ASSERT_EQ(0, rbd.config_list(ioctx, &options)); + for (auto &option : options) { + if (option.name == std::string("rbd_cache")) { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_POOL); + ASSERT_EQ("false", option.value); + } else { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG); + } + } + + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr)); + + options.clear(); + ASSERT_EQ(0, image.config_list(&options)); + for (auto &option : options) { + if (option.name == std::string("rbd_cache")) { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_POOL); + ASSERT_EQ("false", option.value); + } else { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG); + } + } + + ASSERT_EQ(0, image.metadata_set("conf_rbd_cache", "true")); + + options.clear(); + ASSERT_EQ(0, image.config_list(&options)); + for (auto &option : options) { + if (option.name == std::string("rbd_cache")) { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_IMAGE); + ASSERT_EQ("true", option.value); + } else { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG); + } + } + + ASSERT_EQ(0, image.metadata_remove("conf_rbd_cache")); + + options.clear(); + ASSERT_EQ(0, image.config_list(&options)); + for (auto &option : options) { + if (option.name == std::string("rbd_cache")) { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_POOL); + ASSERT_EQ("false", option.value); + } else { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG); + } + } + + ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "conf_rbd_cache")); + + options.clear(); + ASSERT_EQ(0, rbd.config_list(ioctx, &options)); + for (auto &option : options) { + ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG); + } +} + +TEST_F(TestLibRBD, PoolStatsPP) +{ + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(create_pool(true).c_str(), ioctx)); + + librbd::RBD rbd; + std::string image_name; + uint64_t size = 2 << 20; + uint64_t expected_size = 0; + for (size_t idx = 0; idx < 4; ++idx) { + image_name = get_temp_image_name(); + + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, image_name.c_str(), size, &order)); + expected_size += size; + } + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ(0, image.resize(0)); + ASSERT_EQ(0, image.close()); + uint64_t expect_head_size = (expected_size - size); + + uint64_t image_count; + uint64_t provisioned_bytes; + uint64_t max_provisioned_bytes; + uint64_t snap_count; + uint64_t trash_image_count; + uint64_t trash_provisioned_bytes; + uint64_t trash_max_provisioned_bytes; + uint64_t trash_snap_count; + + librbd::PoolStats pool_stats1; + pool_stats1.add(RBD_POOL_STAT_OPTION_IMAGES, &image_count); + pool_stats1.add(RBD_POOL_STAT_OPTION_IMAGE_PROVISIONED_BYTES, + &provisioned_bytes); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats1)); + + ASSERT_EQ(4U, image_count); + ASSERT_EQ(expect_head_size, provisioned_bytes); + + pool_stats1.add(RBD_POOL_STAT_OPTION_IMAGE_MAX_PROVISIONED_BYTES, + &max_provisioned_bytes); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats1)); + ASSERT_EQ(4U, image_count); + ASSERT_EQ(expect_head_size, provisioned_bytes); + ASSERT_EQ(expected_size, max_provisioned_bytes); + + librbd::PoolStats pool_stats2; + pool_stats2.add(RBD_POOL_STAT_OPTION_IMAGE_SNAPSHOTS, &snap_count); + pool_stats2.add(RBD_POOL_STAT_OPTION_TRASH_IMAGES, &trash_image_count); + pool_stats2.add(RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS, &trash_snap_count); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats2)); + ASSERT_EQ(1U, snap_count); + ASSERT_EQ(0U, trash_image_count); + ASSERT_EQ(0U, trash_snap_count); + + ASSERT_EQ(0, rbd.trash_move(ioctx, image_name.c_str(), 0)); + + librbd::PoolStats pool_stats3; + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_IMAGES, &trash_image_count); + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_PROVISIONED_BYTES, + &trash_provisioned_bytes); + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_MAX_PROVISIONED_BYTES, + &trash_max_provisioned_bytes); + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS, &trash_snap_count); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats3)); + ASSERT_EQ(1U, trash_image_count); + ASSERT_EQ(0U, trash_provisioned_bytes); + ASSERT_EQ(size, trash_max_provisioned_bytes); + ASSERT_EQ(1U, trash_snap_count); +} + +TEST_F(TestLibRBD, ImageSpec) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(create_pool(true).c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image parent_image; + std::string name = get_temp_image_name(); + + uint64_t size = 1; + int order = 0; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, parent_image, name.c_str(), NULL)); + + std::string parent_id; + ASSERT_EQ(0, parent_image.get_id(&parent_id)); + + uint64_t features; + ASSERT_EQ(0, parent_image.features(&features)); + + ASSERT_EQ(0, parent_image.snap_create("snap")); + ASSERT_EQ(0, parent_image.snap_protect("snap")); + + std::string clone_name = this->get_temp_image_name(); + ASSERT_EQ(0, rbd.clone(ioctx, name.c_str(), "snap", ioctx, clone_name.c_str(), + features, &order)); + + librbd::Image clone_image; + ASSERT_EQ(0, rbd.open(ioctx, clone_image, clone_name.c_str(), NULL)); + + std::string clone_id; + ASSERT_EQ(0, clone_image.get_id(&clone_id)); + + std::vector<librbd::image_spec_t> images; + ASSERT_EQ(0, rbd.list2(ioctx, &images)); + + std::vector<librbd::image_spec_t> expected_images{ + {.id = parent_id, .name = name}, + {.id = clone_id, .name = clone_name} + }; + std::sort(expected_images.begin(), expected_images.end(), + [](const librbd::image_spec_t& lhs, const librbd::image_spec_t &rhs) { + return lhs.name < rhs.name; + }); + ASSERT_EQ(expected_images, images); + + librbd::linked_image_spec_t parent_image_spec; + librbd::snap_spec_t parent_snap_spec; + ASSERT_EQ(0, clone_image.get_parent(&parent_image_spec, &parent_snap_spec)); + + librbd::linked_image_spec_t expected_parent_image_spec{ + .pool_id = ioctx.get_id(), + .pool_name = ioctx.get_pool_name(), + .pool_namespace = ioctx.get_namespace(), + .image_id = parent_id, + .image_name = name, + .trash = false + }; + ASSERT_EQ(expected_parent_image_spec, parent_image_spec); + ASSERT_EQ(RBD_SNAP_NAMESPACE_TYPE_USER, parent_snap_spec.namespace_type); + ASSERT_EQ("snap", parent_snap_spec.name); + + std::vector<librbd::linked_image_spec_t> children; + ASSERT_EQ(0, parent_image.list_children3(&children)); + + std::vector<librbd::linked_image_spec_t> expected_children{ + { + .pool_id = ioctx.get_id(), + .pool_name = ioctx.get_pool_name(), + .pool_namespace = ioctx.get_namespace(), + .image_id = clone_id, + .image_name = clone_name, + .trash = false + } + }; + ASSERT_EQ(expected_children, children); + + children.clear(); + ASSERT_EQ(0, parent_image.list_descendants(&children)); + ASSERT_EQ(expected_children, children); + + ASSERT_EQ(0, clone_image.snap_create("snap")); + ASSERT_EQ(0, clone_image.snap_protect("snap")); + + auto grand_clone_name = this->get_temp_image_name(); + ASSERT_EQ(0, rbd.clone(ioctx, clone_name.c_str(), "snap", ioctx, + grand_clone_name.c_str(), features, &order)); + librbd::Image grand_clone_image; + ASSERT_EQ(0, rbd.open(ioctx, grand_clone_image, grand_clone_name.c_str(), + nullptr)); + std::string grand_clone_id; + ASSERT_EQ(0, grand_clone_image.get_id(&grand_clone_id)); + + children.clear(); + ASSERT_EQ(0, parent_image.list_children3(&children)); + ASSERT_EQ(expected_children, children); + + children.clear(); + ASSERT_EQ(0, parent_image.list_descendants(&children)); + expected_children.push_back( + { + .pool_id = ioctx.get_id(), + .pool_name = ioctx.get_pool_name(), + .pool_namespace = ioctx.get_namespace(), + .image_id = grand_clone_id, + .image_name = grand_clone_name, + .trash = false + } + ); + ASSERT_EQ(expected_children, children); +} + +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 ℑ + 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 ℑ + 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 ℑ + 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 ℑ + 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> ¤t_state, + const ZTracer::Trace &parent_trace, + bool ignore_enoent, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + s_instance->construct(snap_id, start_object_no, end_object_no, new_state, + current_state, ignore_enoent); + return s_instance; + } + + MOCK_METHOD6(construct, void(uint64_t snap_id, uint64_t start_object_no, + uint64_t end_object_no, uint8_t new_state, + const boost::optional<uint8_t> ¤t_state, + bool ignore_enoent)); + MOCK_METHOD0(send, void()); + UpdateRequest() { + s_instance = this; + } +}; + +RefreshRequest<MockTestImageCtx> *RefreshRequest<MockTestImageCtx>::s_instance = nullptr; +UnlockRequest<MockTestImageCtx> *UnlockRequest<MockTestImageCtx>::s_instance = nullptr; +UpdateRequest<MockTestImageCtx> *UpdateRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace object_map +} // namespace librbd + +#include "librbd/ObjectMap.cc" + +namespace librbd { + +using testing::_; +using testing::InSequence; +using testing::Invoke; + +class TestMockObjectMap : public TestMockFixture { +public: + typedef ObjectMap<MockTestImageCtx> MockObjectMap; + typedef object_map::RefreshRequest<MockTestImageCtx> MockRefreshRequest; + typedef object_map::UnlockRequest<MockTestImageCtx> MockUnlockRequest; + typedef object_map::UpdateRequest<MockTestImageCtx> MockUpdateRequest; + + void expect_refresh(MockTestImageCtx &mock_image_ctx, + MockRefreshRequest &mock_refresh_request, + const ceph::BitVector<2u> &object_map, int r) { + EXPECT_CALL(mock_refresh_request, send()) + .WillOnce(Invoke([&mock_image_ctx, &mock_refresh_request, &object_map, r]() { + *mock_refresh_request.object_map = object_map; + mock_image_ctx.image_ctx->op_work_queue->queue(mock_refresh_request.on_finish, r); + })); + } + + void expect_unlock(MockTestImageCtx &mock_image_ctx, + MockUnlockRequest &mock_unlock_request, int r) { + EXPECT_CALL(mock_unlock_request, send()) + .WillOnce(Invoke([&mock_image_ctx, &mock_unlock_request, r]() { + mock_image_ctx.image_ctx->op_work_queue->queue(mock_unlock_request.on_finish, r); + })); + } + + void expect_update(MockTestImageCtx &mock_image_ctx, + MockUpdateRequest &mock_update_request, + uint64_t snap_id, uint64_t start_object_no, + uint64_t end_object_no, uint8_t new_state, + const boost::optional<uint8_t> ¤t_state, + bool ignore_enoent, Context **on_finish) { + EXPECT_CALL(mock_update_request, construct(snap_id, start_object_no, + end_object_no, new_state, + current_state, ignore_enoent)) + .Times(1); + EXPECT_CALL(mock_update_request, send()) + .WillOnce(Invoke([&mock_update_request, on_finish]() { + *on_finish = mock_update_request.on_finish; + })); + } + +}; + +TEST_F(TestMockObjectMap, NonDetainedUpdate) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + InSequence seq; + ceph::BitVector<2u> object_map; + object_map.resize(4); + MockRefreshRequest mock_refresh_request; + expect_refresh(mock_image_ctx, mock_refresh_request, object_map, 0); + + MockUpdateRequest mock_update_request; + Context *finish_update_1; + expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, + 0, 1, 1, {}, false, &finish_update_1); + Context *finish_update_2; + expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, + 1, 2, 1, {}, false, &finish_update_2); + + MockUnlockRequest mock_unlock_request; + expect_unlock(mock_image_ctx, mock_unlock_request, 0); + + MockObjectMap *mock_object_map = 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(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, RegisterError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + InSequence seq; + expect_aio_watch(mock_image_ctx, -EINVAL); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(-EINVAL, register_ctx.wait()); +} + +TEST_F(TestMockWatcher, UnregisterError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, -EINVAL); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(-EINVAL, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, Reregister) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + // wait for recovery unwatch/watch + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 3)); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterUnwatchError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, -EINVAL); + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + // wait for recovery unwatch/watch + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 3)); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterWatchError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, -EPERM); + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + // wait for recovery unwatch/watch + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 4)); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, 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(®ister_ctx); + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 1)); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -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(®ister_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(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterPendingUnregister) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + // inject an unregister + C_SaferCond unregister_ctx; + expect_aio_watch(mock_image_ctx, 0, + [&mock_image_watcher, &unregister_ctx]() { + mock_image_watcher.unregister_watch(&unregister_ctx); + }); + + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + ASSERT_EQ(0, unregister_ctx.wait()); +} + +} // namespace librbd diff --git a/src/test/librbd/test_mock_fixture.cc b/src/test/librbd/test_mock_fixture.cc new file mode 100644 index 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 |