diff options
Diffstat (limited to 'src/test/librbd/cache')
-rw-r--r-- | src/test/librbd/cache/pwl/test_WriteLogMap.cc | 338 | ||||
-rw-r--r-- | src/test/librbd/cache/pwl/test_mock_ReplicatedWriteLog.cc | 743 | ||||
-rw-r--r-- | src/test/librbd/cache/pwl/test_mock_SSDWriteLog.cc | 761 | ||||
-rw-r--r-- | src/test/librbd/cache/test_mock_ParentCacheObjectDispatch.cc | 427 | ||||
-rw-r--r-- | src/test/librbd/cache/test_mock_WriteAroundObjectDispatch.cc | 703 |
5 files changed, 2972 insertions, 0 deletions
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..1cafb00ff --- /dev/null +++ b/src/test/librbd/cache/pwl/test_WriteLogMap.cc @@ -0,0 +1,338 @@ +// -*- 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 { + +using namespace std; + +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 |