From 19fcec84d8d7d21e796c7624e521b60d28ee21ed Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:45:59 +0200 Subject: Adding upstream version 16.2.11+ds. Signed-off-by: Daniel Baumann --- src/test/librbd/journal/test_Entries.cc | 227 +++ src/test/librbd/journal/test_Replay.cc | 886 +++++++++ src/test/librbd/journal/test_mock_OpenRequest.cc | 193 ++ .../librbd/journal/test_mock_PromoteRequest.cc | 356 ++++ src/test/librbd/journal/test_mock_Replay.cc | 2042 ++++++++++++++++++++ src/test/librbd/journal/test_mock_ResetRequest.cc | 278 +++ 6 files changed, 3982 insertions(+) create mode 100644 src/test/librbd/journal/test_Entries.cc create mode 100644 src/test/librbd/journal/test_Replay.cc create mode 100644 src/test/librbd/journal/test_mock_OpenRequest.cc create mode 100644 src/test/librbd/journal/test_mock_PromoteRequest.cc create mode 100644 src/test/librbd/journal/test_mock_Replay.cc create mode 100644 src/test/librbd/journal/test_mock_ResetRequest.cc (limited to 'src/test/librbd/journal') diff --git a/src/test/librbd/journal/test_Entries.cc b/src/test/librbd/journal/test_Entries.cc new file mode 100644 index 000000000..d89c4f8e4 --- /dev/null +++ b/src/test/librbd/journal/test_Entries.cc @@ -0,0 +1,227 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "librbd/internal.h" +#include "librbd/Journal.h" +#include "librbd/api/Io.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/journal/Types.h" +#include "journal/Journaler.h" +#include "journal/ReplayEntry.h" +#include "journal/ReplayHandler.h" +#include "journal/Settings.h" +#include +#include + +void register_test_journal_entries() { +} + +namespace librbd { +namespace journal { + +class TestJournalEntries : public TestFixture { +public: + typedef std::list<::journal::Journaler *> Journalers; + + struct ReplayHandler : public ::journal::ReplayHandler { + ceph::mutex lock = ceph::make_mutex("ReplayHandler::lock"); + ceph::condition_variable cond; + bool entries_available; + bool complete; + + ReplayHandler() + : entries_available(false), complete(false) { + } + + void handle_entries_available() override { + std::lock_guard locker{lock}; + entries_available = true; + cond.notify_all(); + } + + void handle_complete(int r) override { + std::lock_guard locker{lock}; + complete = true; + cond.notify_all(); + } + }; + + ReplayHandler m_replay_handler; + Journalers m_journalers; + + void TearDown() override { + for (Journalers::iterator it = m_journalers.begin(); + it != m_journalers.end(); ++it) { + ::journal::Journaler *journaler = *it; + journaler->stop_replay(); + journaler->shut_down(); + delete journaler; + } + + TestFixture::TearDown(); + } + + ::journal::Journaler *create_journaler(librbd::ImageCtx *ictx) { + ::journal::Journaler *journaler = new ::journal::Journaler( + ictx->md_ctx, ictx->id, "dummy client", {}, nullptr); + + int r = journaler->register_client(bufferlist()); + if (r < 0) { + ADD_FAILURE() << "failed to register journal client"; + delete journaler; + return NULL; + } + + C_SaferCond cond; + journaler->init(&cond); + r = cond.wait(); + if (r < 0) { + ADD_FAILURE() << "failed to initialize journal client"; + delete journaler; + return NULL; + } + + journaler->start_live_replay(&m_replay_handler, 0.1); + m_journalers.push_back(journaler); + return journaler; + } + + bool wait_for_entries_available(librbd::ImageCtx *ictx) { + std::unique_lock locker{m_replay_handler.lock}; + while (!m_replay_handler.entries_available) { + if (m_replay_handler.cond.wait_for(locker, 10s) == std::cv_status::timeout) { + return false; + } + } + m_replay_handler.entries_available = false; + return true; + } + + bool get_event_entry(const ::journal::ReplayEntry &replay_entry, + librbd::journal::EventEntry *event_entry) { + try { + bufferlist data_bl = replay_entry.get_data(); + auto it = data_bl.cbegin(); + decode(*event_entry, it); + } catch (const buffer::error &err) { + return false; + } + return true; + } + +}; + +TEST_F(TestJournalEntries, AioWrite) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ::journal::Journaler *journaler = create_journaler(ictx); + ASSERT_TRUE(journaler != NULL); + + std::string buffer(512, '1'); + bufferlist write_bl; + write_bl.append(buffer); + + C_SaferCond cond_ctx; + auto c = librbd::io::AioCompletion::create(&cond_ctx); + c->get(); + api::Io<>::aio_write(*ictx, c, 123, buffer.size(), std::move(write_bl), 0, + true); + ASSERT_EQ(0, c->wait_for_complete()); + c->put(); + + ASSERT_TRUE(wait_for_entries_available(ictx)); + + ::journal::ReplayEntry replay_entry; + ASSERT_TRUE(journaler->try_pop_front(&replay_entry)); + + librbd::journal::EventEntry event_entry; + ASSERT_TRUE(get_event_entry(replay_entry, &event_entry)); + + ASSERT_EQ(librbd::journal::EVENT_TYPE_AIO_WRITE, + event_entry.get_event_type()); + + librbd::journal::AioWriteEvent aio_write_event = + boost::get(event_entry.event); + ASSERT_EQ(123U, aio_write_event.offset); + ASSERT_EQ(buffer.size(), aio_write_event.length); + + bufferlist buffer_bl; + buffer_bl.append(buffer); + ASSERT_TRUE(aio_write_event.data.contents_equal(buffer_bl)); + + ASSERT_EQ(librbd::journal::AioWriteEvent::get_fixed_size() + + aio_write_event.data.length(), replay_entry.get_data().length()); +} + +TEST_F(TestJournalEntries, AioDiscard) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + CephContext* cct = reinterpret_cast(_rados.cct()); + REQUIRE(!cct->_conf.get_val("rbd_skip_partial_discard")); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ::journal::Journaler *journaler = create_journaler(ictx); + ASSERT_TRUE(journaler != NULL); + + C_SaferCond cond_ctx; + auto c = librbd::io::AioCompletion::create(&cond_ctx); + c->get(); + api::Io<>::aio_discard(*ictx, c, 123, 234, ictx->discard_granularity_bytes, + true); + ASSERT_EQ(0, c->wait_for_complete()); + c->put(); + + ASSERT_TRUE(wait_for_entries_available(ictx)); + + ::journal::ReplayEntry replay_entry; + ASSERT_TRUE(journaler->try_pop_front(&replay_entry)); + + librbd::journal::EventEntry event_entry; + ASSERT_TRUE(get_event_entry(replay_entry, &event_entry)); + + ASSERT_EQ(librbd::journal::EVENT_TYPE_AIO_DISCARD, + event_entry.get_event_type()); + + librbd::journal::AioDiscardEvent aio_discard_event = + boost::get(event_entry.event); + ASSERT_EQ(123U, aio_discard_event.offset); + ASSERT_EQ(234U, aio_discard_event.length); +} + +TEST_F(TestJournalEntries, AioFlush) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ::journal::Journaler *journaler = create_journaler(ictx); + ASSERT_TRUE(journaler != NULL); + + C_SaferCond cond_ctx; + auto c = librbd::io::AioCompletion::create(&cond_ctx); + c->get(); + api::Io<>::aio_flush(*ictx, c, true); + ASSERT_EQ(0, c->wait_for_complete()); + c->put(); + + ASSERT_TRUE(wait_for_entries_available(ictx)); + + ::journal::ReplayEntry replay_entry; + ASSERT_TRUE(journaler->try_pop_front(&replay_entry)); + + librbd::journal::EventEntry event_entry; + ASSERT_TRUE(get_event_entry(replay_entry, &event_entry)); + + ASSERT_EQ(librbd::journal::EVENT_TYPE_AIO_FLUSH, + event_entry.get_event_type()); +} + +} // namespace journal +} // namespace librbd diff --git a/src/test/librbd/journal/test_Replay.cc b/src/test/librbd/journal/test_Replay.cc new file mode 100644 index 000000000..9b4580e64 --- /dev/null +++ b/src/test/librbd/journal/test_Replay.cc @@ -0,0 +1,886 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "cls/rbd/cls_rbd_types.h" +#include "cls/journal/cls_journal_types.h" +#include "cls/journal/cls_journal_client.h" +#include "journal/Journaler.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/ImageWatcher.h" +#include "librbd/internal.h" +#include "librbd/Journal.h" +#include "librbd/Operations.h" +#include "librbd/api/Io.h" +#include "librbd/api/Snapshot.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/io/ImageDispatchSpec.h" +#include "librbd/io/ImageRequest.h" +#include "librbd/io/ReadResult.h" +#include "librbd/journal/Types.h" + +void register_test_journal_replay() { +} + +namespace librbd { +namespace journal { + +class TestJournalReplay : public TestFixture { +public: + + int when_acquired_lock(librbd::ImageCtx *ictx) { + C_SaferCond lock_ctx; + { + std::unique_lock owner_locker{ictx->owner_lock}; + ictx->exclusive_lock->acquire_lock(&lock_ctx); + } + int r = lock_ctx.wait(); + if (r < 0) { + return r; + } + + C_SaferCond refresh_ctx; + ictx->state->refresh(&refresh_ctx); + return refresh_ctx.wait(); + } + + template + void inject_into_journal(librbd::ImageCtx *ictx, T event) { + C_SaferCond ctx; + librbd::journal::EventEntry event_entry(event); + { + std::shared_lock owner_locker{ictx->owner_lock}; + uint64_t tid = ictx->journal->append_io_event(std::move(event_entry),0, 0, + true, 0); + ictx->journal->wait_event(tid, &ctx); + } + ASSERT_EQ(0, ctx.wait()); + } + + void get_journal_commit_position(librbd::ImageCtx *ictx, int64_t *tag, + int64_t *entry) + { + const std::string client_id = ""; + std::string journal_id = ictx->id; + + C_SaferCond close_cond; + ictx->journal->close(&close_cond); + ASSERT_EQ(0, close_cond.wait()); + + ictx->journal->put(); + ictx->journal = nullptr; + + C_SaferCond cond; + uint64_t minimum_set; + uint64_t active_set; + std::set registered_clients; + std::string oid = ::journal::Journaler::header_oid(journal_id); + cls::journal::client::get_mutable_metadata(ictx->md_ctx, oid, &minimum_set, + &active_set, ®istered_clients, &cond); + ASSERT_EQ(0, cond.wait()); + std::set::const_iterator c; + for (c = registered_clients.begin(); c != registered_clients.end(); ++c) { + if (c->id == client_id) { + break; + } + } + if (c == registered_clients.end() || + c->commit_position.object_positions.empty()) { + *tag = 0; + *entry = -1; + } else { + const cls::journal::ObjectPosition &object_position = + *c->commit_position.object_positions.begin(); + *tag = object_position.tag_tid; + *entry = object_position.entry_tid; + } + + C_SaferCond open_cond; + ictx->journal = new librbd::Journal<>(*ictx); + ictx->journal->open(&open_cond); + ASSERT_EQ(0, open_cond.wait()); + } +}; + +TEST_F(TestJournalReplay, AioDiscardEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + // write to the image w/o using the journal + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->features &= ~RBD_FEATURE_JOURNALING; + + std::string payload(4096, '1'); + bufferlist payload_bl; + payload_bl.append(payload); + auto aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_write(*ictx, aio_comp, 0, payload.size(), + std::move(payload_bl), 0, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_flush(*ictx, aio_comp, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + std::string read_payload(4096, '\0'); + librbd::io::ReadResult read_result{&read_payload[0], read_payload.size()}; + aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_read(*ictx, aio_comp, 0, read_payload.size(), + librbd::io::ReadResult{read_result}, 0, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + ASSERT_EQ(payload, read_payload); + close_image(ictx); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject a discard operation into the journal + inject_into_journal(ictx, + librbd::journal::AioDiscardEvent( + 0, payload.size(), ictx->discard_granularity_bytes)); + close_image(ictx); + + // re-open the journal so that it replays the new entry + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_read(*ictx, aio_comp, 0, read_payload.size(), + librbd::io::ReadResult{read_result}, 0, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + if (ictx->discard_granularity_bytes > 0) { + ASSERT_EQ(payload, read_payload); + } else { + ASSERT_EQ(std::string(read_payload.size(), '\0'), read_payload); + } + + // check the commit position is properly updated + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + // replay several envents and check the commit position + inject_into_journal(ictx, + librbd::journal::AioDiscardEvent( + 0, payload.size(), ictx->discard_granularity_bytes)); + inject_into_journal(ictx, + librbd::journal::AioDiscardEvent( + 0, payload.size(), ictx->discard_granularity_bytes)); + close_image(ictx); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 2, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_discard(*ictx, aio_comp, 0, read_payload.size(), + ictx->discard_granularity_bytes, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); +} + +TEST_F(TestJournalReplay, AioWriteEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject a write operation into the journal + std::string payload(4096, '1'); + bufferlist payload_bl; + payload_bl.append(payload); + inject_into_journal(ictx, + librbd::journal::AioWriteEvent(0, payload.size(), payload_bl)); + close_image(ictx); + + // re-open the journal so that it replays the new entry + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + std::string read_payload(4096, '\0'); + librbd::io::ReadResult read_result{&read_payload[0], read_payload.size()}; + auto aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_read(*ictx, aio_comp, 0, read_payload.size(), + std::move(read_result), 0, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + ASSERT_EQ(payload, read_payload); + + // check the commit position is properly updated + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + // replay several events and check the commit position + inject_into_journal(ictx, + librbd::journal::AioWriteEvent(0, payload.size(), payload_bl)); + inject_into_journal(ictx, + librbd::journal::AioWriteEvent(0, payload.size(), payload_bl)); + close_image(ictx); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 2, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_write(*ictx, aio_comp, 0, payload.size(), + bufferlist{payload_bl}, 0, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); +} + +TEST_F(TestJournalReplay, AioFlushEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject a flush operation into the journal + inject_into_journal(ictx, librbd::journal::AioFlushEvent()); + close_image(ictx); + + // re-open the journal so that it replays the new entry + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // check the commit position is properly updated + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + // replay several events and check the commit position + inject_into_journal(ictx, librbd::journal::AioFlushEvent()); + inject_into_journal(ictx, librbd::journal::AioFlushEvent()); + close_image(ictx); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 2, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + auto aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_flush(*ictx, aio_comp, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); +} + +TEST_F(TestJournalReplay, SnapCreate) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, librbd::journal::SnapCreateEvent(1, cls::rbd::UserSnapshotNamespace(), + "snap")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + { + std::shared_lock image_locker{ictx->image_lock}; + ASSERT_NE(CEPH_NOSNAP, ictx->get_snap_id(cls::rbd::UserSnapshotNamespace(), + "snap")); + } + + // verify lock ordering constraints + librbd::NoOpProgressContext no_op_progress; + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap2", 0, no_op_progress)); +} + +TEST_F(TestJournalReplay, SnapProtect) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + librbd::NoOpProgressContext no_op_progress; + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap", 0, no_op_progress)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, + librbd::journal::SnapProtectEvent(1, + cls::rbd::UserSnapshotNamespace(), + "snap")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + + bool is_protected; + ASSERT_EQ(0, librbd::api::Snapshot<>::is_protected(ictx, "snap", &is_protected)); + ASSERT_TRUE(is_protected); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap2", 0, no_op_progress)); + ASSERT_EQ(0, ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap2")); +} + +TEST_F(TestJournalReplay, SnapUnprotect) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + librbd::NoOpProgressContext no_op_progress; + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap", 0, no_op_progress)); + uint64_t snap_id; + { + std::shared_lock image_locker{ictx->image_lock}; + snap_id = ictx->get_snap_id(cls::rbd::UserSnapshotNamespace(), "snap"); + ASSERT_NE(CEPH_NOSNAP, snap_id); + } + ASSERT_EQ(0, ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap")); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, + librbd::journal::SnapUnprotectEvent(1, + cls::rbd::UserSnapshotNamespace(), + "snap")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + + bool is_protected; + ASSERT_EQ(0, librbd::api::Snapshot<>::is_protected(ictx, "snap", &is_protected)); + ASSERT_FALSE(is_protected); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap2", 0, no_op_progress)); + ASSERT_EQ(0, ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap2")); + ASSERT_EQ(0, ictx->operations->snap_unprotect(cls::rbd::UserSnapshotNamespace(), + "snap2")); +} + +TEST_F(TestJournalReplay, SnapRename) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + librbd::NoOpProgressContext no_op_progress; + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap", 0, no_op_progress)); + uint64_t snap_id; + { + std::shared_lock image_locker{ictx->image_lock}; + snap_id = ictx->get_snap_id(cls::rbd::UserSnapshotNamespace(), "snap"); + ASSERT_NE(CEPH_NOSNAP, snap_id); + } + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, librbd::journal::SnapRenameEvent(1, snap_id, "snap", + "snap2")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + ASSERT_EQ(0, ictx->state->refresh()); + + { + std::shared_lock image_locker{ictx->image_lock}; + snap_id = ictx->get_snap_id(cls::rbd::UserSnapshotNamespace(), "snap2"); + ASSERT_NE(CEPH_NOSNAP, snap_id); + } + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->snap_rename("snap2", "snap3")); +} + +TEST_F(TestJournalReplay, SnapRollback) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + librbd::NoOpProgressContext no_op_progress; + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap", 0, no_op_progress)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, + librbd::journal::SnapRollbackEvent(1, + cls::rbd::UserSnapshotNamespace(), + "snap")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->snap_rollback(cls::rbd::UserSnapshotNamespace(), + "snap", + no_op_progress)); +} + +TEST_F(TestJournalReplay, SnapRemove) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + librbd::NoOpProgressContext no_op_progress; + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap", 0, no_op_progress)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, + librbd::journal::SnapRemoveEvent(1, + cls::rbd::UserSnapshotNamespace(), + "snap")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + + { + std::shared_lock image_locker{ictx->image_lock}; + uint64_t snap_id = ictx->get_snap_id(cls::rbd::UserSnapshotNamespace(), + "snap"); + ASSERT_EQ(CEPH_NOSNAP, snap_id); + } + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap", 0, no_op_progress)); + ASSERT_EQ(0, ictx->operations->snap_remove(cls::rbd::UserSnapshotNamespace(), + "snap")); +} + +TEST_F(TestJournalReplay, Rename) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + std::string new_image_name(get_temp_image_name()); + inject_into_journal(ictx, librbd::journal::RenameEvent(1, new_image_name)); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + librbd::RBD rbd; + ASSERT_EQ(0, rbd.rename(m_ioctx, new_image_name.c_str(), m_image_name.c_str())); +} + +TEST_F(TestJournalReplay, Resize) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx, librbd::journal::ResizeEvent(1, 16)); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + // verify lock ordering constraints + librbd::NoOpProgressContext no_op_progress; + ASSERT_EQ(0, ictx->operations->resize(0, true, no_op_progress)); +} + +TEST_F(TestJournalReplay, Flatten) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + librbd::NoOpProgressContext no_op_progress; + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "snap", 0, no_op_progress)); + ASSERT_EQ(0, ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "snap")); + + std::string clone_name = get_temp_image_name(); + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap", m_ioctx, + clone_name.c_str(), ictx->features, &order, 0, 0)); + + librbd::ImageCtx *ictx2; + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + ASSERT_EQ(0, when_acquired_lock(ictx2)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx2, &initial_tag, &initial_entry); + + // inject snapshot ops into journal + inject_into_journal(ictx2, librbd::journal::FlattenEvent(1)); + inject_into_journal(ictx2, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx2); + + // replay journal + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + ASSERT_EQ(0, when_acquired_lock(ictx2)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx2, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + ASSERT_EQ(0, ictx->operations->snap_unprotect(cls::rbd::UserSnapshotNamespace(), + "snap")); + + // verify lock ordering constraints + librbd::NoOpProgressContext no_op; + ASSERT_EQ(-EINVAL, ictx2->operations->flatten(no_op)); +} + +TEST_F(TestJournalReplay, UpdateFeatures) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + uint64_t features = RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF; + bool enabled = !ictx->test_features(features); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject update_features op into journal + inject_into_journal(ictx, librbd::journal::UpdateFeaturesEvent(1, features, + enabled)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(0, current_entry); + + ASSERT_EQ(enabled, ictx->test_features(features)); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->update_features(features, !enabled)); +} + +TEST_F(TestJournalReplay, MetadataSet) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject metadata_set op into journal + inject_into_journal(ictx, librbd::journal::MetadataSetEvent( + 1, "conf_rbd_mirroring_replay_delay", "9876")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + ASSERT_EQ(9876U, ictx->mirroring_replay_delay); + + std::string value; + ASSERT_EQ(0, librbd::metadata_get(ictx, "conf_rbd_mirroring_replay_delay", + &value)); + ASSERT_EQ("9876", value); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->metadata_set("key2", "value")); +} + +TEST_F(TestJournalReplay, MetadataRemove) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + ASSERT_EQ(0, ictx->operations->metadata_set( + "conf_rbd_mirroring_replay_delay", "9876")); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + // inject metadata_remove op into journal + inject_into_journal(ictx, librbd::journal::MetadataRemoveEvent( + 1, "conf_rbd_mirroring_replay_delay")); + inject_into_journal(ictx, librbd::journal::OpFinishEvent(1, 0)); + close_image(ictx); + + // replay journal + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag, current_tag); + ASSERT_EQ(initial_entry + 2, current_entry); + ASSERT_EQ(0U, ictx->mirroring_replay_delay); + + std::string value; + ASSERT_EQ(-ENOENT, + librbd::metadata_get(ictx, "conf_rbd_mirroring_replay_delay", + &value)); + + // verify lock ordering constraints + ASSERT_EQ(0, ictx->operations->metadata_set("key", "value")); + ASSERT_EQ(0, ictx->operations->metadata_remove("key")); +} + +TEST_F(TestJournalReplay, ObjectPosition) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, when_acquired_lock(ictx)); + + // get current commit position + int64_t initial_tag; + int64_t initial_entry; + get_journal_commit_position(ictx, &initial_tag, &initial_entry); + + std::string payload(4096, '1'); + bufferlist payload_bl; + payload_bl.append(payload); + auto aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_write(*ictx, aio_comp, 0, payload.size(), + bufferlist{payload_bl}, 0, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_flush(*ictx, aio_comp, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + // check the commit position updated + int64_t current_tag; + int64_t current_entry; + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(1, current_entry); + + // write again + + aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_write(*ictx, aio_comp, 0, payload.size(), + bufferlist{payload_bl}, 0, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_flush(*ictx, aio_comp, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + + // user flush requests are ignored when journaling + cache are enabled + C_SaferCond flush_ctx; + aio_comp = librbd::io::AioCompletion::create_and_start( + &flush_ctx, ictx, librbd::io::AIO_TYPE_FLUSH); + auto req = librbd::io::ImageDispatchSpec::create_flush( + *ictx, librbd::io::IMAGE_DISPATCH_LAYER_INTERNAL_START, aio_comp, + librbd::io::FLUSH_SOURCE_INTERNAL, {}); + req->send(); + ASSERT_EQ(0, flush_ctx.wait()); + + // check the commit position updated + get_journal_commit_position(ictx, ¤t_tag, ¤t_entry); + ASSERT_EQ(initial_tag + 1, current_tag); + ASSERT_EQ(3, current_entry); +} + +} // namespace journal +} // namespace librbd diff --git a/src/test/librbd/journal/test_mock_OpenRequest.cc b/src/test/librbd/journal/test_mock_OpenRequest.cc new file mode 100644 index 000000000..0d86f20e9 --- /dev/null +++ b/src/test/librbd/journal/test_mock_OpenRequest.cc @@ -0,0 +1,193 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/journal/mock/MockJournaler.h" +#include "common/ceph_mutex.h" +#include "cls/journal/cls_journal_types.h" +#include "librbd/journal/OpenRequest.h" +#include "librbd/journal/Types.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace journal { + +template <> +struct TypeTraits { + typedef ::journal::MockJournaler Journaler; +}; + +} // namespace journal +} // namespace librbd + +// template definitions +#include "librbd/journal/OpenRequest.cc" +template class librbd::journal::OpenRequest; + +namespace librbd { +namespace journal { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::WithArg; + +class TestMockJournalOpenRequest : public TestMockFixture { +public: + typedef OpenRequest MockOpenRequest; + + TestMockJournalOpenRequest() = default; + + void expect_init_journaler(::journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, init(_)) + .WillOnce(CompleteContext(r, static_cast(NULL))); + } + + void expect_get_journaler_cached_client(::journal::MockJournaler &mock_journaler, + int r) { + journal::ImageClientMeta image_client_meta; + image_client_meta.tag_class = 345; + + journal::ClientData client_data; + client_data.client_meta = image_client_meta; + + cls::journal::Client client; + encode(client_data, client.data); + + EXPECT_CALL(mock_journaler, get_cached_client("", _)) + .WillOnce(DoAll(SetArgPointee<1>(client), + Return(r))); + } + + void expect_get_journaler_tags(MockImageCtx &mock_image_ctx, + ::journal::MockJournaler &mock_journaler, + int r) { + journal::TagData tag_data; + tag_data.mirror_uuid = "remote mirror"; + + bufferlist tag_data_bl; + encode(tag_data, tag_data_bl); + + ::journal::Journaler::Tags tags = {{0, 345, {}}, {1, 345, tag_data_bl}}; + EXPECT_CALL(mock_journaler, get_tags(345, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(tags), + WithArg<2>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)))); + } + + ceph::mutex m_lock = ceph::make_mutex("m_lock"); + ImageClientMeta m_client_meta; + uint64_t m_tag_tid = 0; + TagData m_tag_data; +}; + +TEST_F(TestMockJournalOpenRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_init_journaler(mock_journaler, 0); + expect_get_journaler_cached_client(mock_journaler, 0); + expect_get_journaler_tags(mock_image_ctx, mock_journaler, 0); + + C_SaferCond ctx; + auto req = MockOpenRequest::create(&mock_image_ctx, &mock_journaler, + &m_lock, &m_client_meta, &m_tag_tid, + &m_tag_data, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(345U, m_client_meta.tag_class); + ASSERT_EQ(1U, m_tag_tid); + ASSERT_EQ("remote mirror", m_tag_data.mirror_uuid); +} + +TEST_F(TestMockJournalOpenRequest, InitError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_init_journaler(mock_journaler, -ENOENT); + + C_SaferCond ctx; + auto req = MockOpenRequest::create(&mock_image_ctx, &mock_journaler, + &m_lock, &m_client_meta, &m_tag_tid, + &m_tag_data, &ctx); + req->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockJournalOpenRequest, GetCachedClientError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_init_journaler(mock_journaler, 0); + expect_get_journaler_cached_client(mock_journaler, -EINVAL); + + C_SaferCond ctx; + auto req = MockOpenRequest::create(&mock_image_ctx, &mock_journaler, + &m_lock, &m_client_meta, &m_tag_tid, + &m_tag_data, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockJournalOpenRequest, GetTagsError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_init_journaler(mock_journaler, 0); + expect_get_journaler_cached_client(mock_journaler, 0); + expect_get_journaler_tags(mock_image_ctx, mock_journaler, -EBADMSG); + + C_SaferCond ctx; + auto req = MockOpenRequest::create(&mock_image_ctx, &mock_journaler, + &m_lock, &m_client_meta, &m_tag_tid, + &m_tag_data, &ctx); + req->send(); + ASSERT_EQ(-EBADMSG, ctx.wait()); +} + +} // namespace journal +} // namespace librbd diff --git a/src/test/librbd/journal/test_mock_PromoteRequest.cc b/src/test/librbd/journal/test_mock_PromoteRequest.cc new file mode 100644 index 000000000..c4e7ed9dc --- /dev/null +++ b/src/test/librbd/journal/test_mock_PromoteRequest.cc @@ -0,0 +1,356 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/journal/mock/MockJournaler.h" +#include "librbd/journal/OpenRequest.h" +#include "librbd/journal/PromoteRequest.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace journal { + +template <> +struct TypeTraits { + typedef ::journal::MockJournalerProxy Journaler; + typedef ::journal::MockFutureProxy Future; +}; + +template <> +struct OpenRequest { + Context *on_finish = nullptr; + static OpenRequest *s_instance; + static OpenRequest *create(MockTestImageCtx *image_ctx, + ::journal::MockJournalerProxy *journaler, + ceph::mutex *lock, ImageClientMeta *client_meta, + uint64_t *tag_tid, journal::TagData *tag_data, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + client_meta->tag_class = 456; + tag_data->mirror_uuid = Journal<>::ORPHAN_MIRROR_UUID; + *tag_tid = 567; + s_instance->on_finish = on_finish; + return s_instance; + } + + OpenRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +OpenRequest *OpenRequest::s_instance = nullptr; + +} // namespace journal +} // namespace librbd + +// template definitions +#include "librbd/journal/PromoteRequest.cc" +template class librbd::journal::PromoteRequest; + +namespace librbd { +namespace journal { + +using ::testing::_; +using ::testing::A; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::WithArg; + +class TestMockJournalPromoteRequest : public TestMockFixture { +public: + typedef PromoteRequest MockPromoteRequest; + typedef OpenRequest MockOpenRequest; + + void expect_construct_journaler(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, construct()); + } + + void expect_open_journaler(MockTestImageCtx &mock_image_ctx, + MockOpenRequest &mock_open_request, int r) { + EXPECT_CALL(mock_open_request, send()) + .WillOnce(FinishRequest(&mock_open_request, r, &mock_image_ctx)); + } + + void expect_allocate_tag(::journal::MockJournaler &mock_journaler, + const journal::TagPredecessor &predecessor, int r) { + TagData tag_data; + tag_data.mirror_uuid = Journal<>::LOCAL_MIRROR_UUID; + tag_data.predecessor = predecessor; + + bufferlist tag_data_bl; + using ceph::encode; + encode(tag_data, tag_data_bl); + + EXPECT_CALL(mock_journaler, allocate_tag(456, ContentsEqual(tag_data_bl), + _, _)) + .WillOnce(WithArg<3>(CompleteContext(r, static_cast(NULL)))); + } + + void expect_append_journaler(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, append(_, _)) + .WillOnce(Return(::journal::MockFutureProxy())); + } + + void expect_future_flush(::journal::MockFuture &mock_future, int r) { + EXPECT_CALL(mock_future, flush(_)) + .WillOnce(CompleteContext(r, static_cast(NULL))); + } + + void expect_future_committed(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, committed(A())); + } + + void expect_flush_commit_position(::journal::MockJournaler &mock_journaler, + int r) { + EXPECT_CALL(mock_journaler, flush_commit_position(_)) + .WillOnce(CompleteContext(r, static_cast(NULL))); + } + + void expect_start_append(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, start_append(_)); + } + + void expect_stop_append(::journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, stop_append(_)) + .WillOnce(CompleteContext(r, static_cast(NULL))); + } + + void expect_shut_down_journaler(::journal::MockJournaler &mock_journaler, + int r) { + EXPECT_CALL(mock_journaler, shut_down(_)) + .WillOnce(CompleteContext(r, static_cast(NULL))); + } + +}; + +TEST_F(TestMockJournalPromoteRequest, SuccessOrderly) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::ORPHAN_MIRROR_UUID, true, 567, 1}, 0); + + ::journal::MockFuture mock_future; + expect_start_append(mock_journaler); + expect_append_journaler(mock_journaler); + expect_future_flush(mock_future, 0); + expect_future_committed(mock_journaler); + expect_flush_commit_position(mock_journaler, 0); + expect_stop_append(mock_journaler, 0); + + expect_shut_down_journaler(mock_journaler, 0); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, SuccessForced) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::LOCAL_MIRROR_UUID, true, 567, 0}, 0); + + ::journal::MockFuture mock_future; + expect_start_append(mock_journaler); + expect_append_journaler(mock_journaler); + expect_future_flush(mock_future, 0); + expect_future_committed(mock_journaler); + expect_flush_commit_position(mock_journaler, 0); + expect_stop_append(mock_journaler, 0); + + expect_shut_down_journaler(mock_journaler, 0); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, OpenError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, -ENOENT); + expect_shut_down_journaler(mock_journaler, -EINVAL); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, false, &ctx); + req->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, AllocateTagError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::LOCAL_MIRROR_UUID, true, 567, 0}, -EBADMSG); + expect_shut_down_journaler(mock_journaler, -EINVAL); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, true, &ctx); + req->send(); + ASSERT_EQ(-EBADMSG, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, AppendEventError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::ORPHAN_MIRROR_UUID, true, 567, 1}, 0); + + ::journal::MockFuture mock_future; + expect_start_append(mock_journaler); + expect_append_journaler(mock_journaler); + expect_future_flush(mock_future, -EPERM); + expect_stop_append(mock_journaler, 0); + + expect_shut_down_journaler(mock_journaler, 0); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, false, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, CommitEventError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::ORPHAN_MIRROR_UUID, true, 567, 1}, 0); + + ::journal::MockFuture mock_future; + expect_start_append(mock_journaler); + expect_append_journaler(mock_journaler); + expect_future_flush(mock_future, 0); + expect_future_committed(mock_journaler); + expect_flush_commit_position(mock_journaler, -EINVAL); + expect_stop_append(mock_journaler, 0); + + expect_shut_down_journaler(mock_journaler, 0); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockJournalPromoteRequest, ShutDownError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + ::journal::MockJournaler mock_journaler; + MockOpenRequest mock_open_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_construct_journaler(mock_journaler); + expect_open_journaler(mock_image_ctx, mock_open_request, 0); + expect_allocate_tag(mock_journaler, + {Journal<>::LOCAL_MIRROR_UUID, true, 567, 0}, 0); + + ::journal::MockFuture mock_future; + expect_start_append(mock_journaler); + expect_append_journaler(mock_journaler); + expect_future_flush(mock_future, 0); + expect_future_committed(mock_journaler); + expect_flush_commit_position(mock_journaler, 0); + expect_stop_append(mock_journaler, 0); + + expect_shut_down_journaler(mock_journaler, -EINVAL); + + C_SaferCond ctx; + auto req = MockPromoteRequest::create(&mock_image_ctx, true, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace journal +} // namespace librbd diff --git a/src/test/librbd/journal/test_mock_Replay.cc b/src/test/librbd/journal/test_mock_Replay.cc new file mode 100644 index 000000000..b902e0f95 --- /dev/null +++ b/src/test/librbd/journal/test_mock_Replay.cc @@ -0,0 +1,2042 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "librbd/io/ImageRequest.h" +#include "librbd/journal/Replay.h" +#include "librbd/journal/Types.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include + +namespace librbd { + +namespace { + +struct MockReplayImageCtx : public MockImageCtx { + explicit MockReplayImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace io { + +template <> +struct ImageRequest { + static ImageRequest *s_instance; + + MOCK_METHOD4(aio_write, void(AioCompletion *c, const Extents &image_extents, + const bufferlist &bl, int op_flags)); + static void aio_write(MockReplayImageCtx *ictx, AioCompletion *c, + Extents &&image_extents, bufferlist &&bl, + IOContext io_context, int op_flags, + const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_write(c, image_extents, bl, op_flags); + } + + MOCK_METHOD3(aio_discard, void(AioCompletion *c, const Extents& image_extents, + uint32_t discard_granularity_bytes)); + static void aio_discard(MockReplayImageCtx *ictx, AioCompletion *c, + Extents&& image_extents, + uint32_t discard_granularity_bytes, + IOContext io_context, + const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_discard(c, image_extents, discard_granularity_bytes); + } + + MOCK_METHOD1(aio_flush, void(AioCompletion *c)); + static void aio_flush(MockReplayImageCtx *ictx, AioCompletion *c, + FlushSource, const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_flush(c); + } + + MOCK_METHOD4(aio_writesame, void(AioCompletion *c, + const Extents& image_extents, + const bufferlist &bl, + int op_flags)); + static void aio_writesame(MockReplayImageCtx *ictx, AioCompletion *c, + Extents&& image_extents, bufferlist &&bl, + IOContext io_context, int op_flags, + const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_writesame(c, image_extents, bl, op_flags); + } + + MOCK_METHOD6(aio_compare_and_write, void(AioCompletion *c, const Extents &image_extents, + const bufferlist &cmp_bl, const bufferlist &bl, + uint64_t *mismatch_offset, + int op_flags)); + static void aio_compare_and_write(MockReplayImageCtx *ictx, AioCompletion *c, + Extents &&image_extents, bufferlist &&cmp_bl, + bufferlist &&bl, uint64_t *mismatch_offset, + IOContext io_context, int op_flags, + const ZTracer::Trace &parent_trace) { + ceph_assert(s_instance != nullptr); + s_instance->aio_compare_and_write(c, image_extents, cmp_bl, bl, + mismatch_offset, op_flags); + } + + ImageRequest() { + s_instance = this; + } +}; + +ImageRequest *ImageRequest::s_instance = nullptr; + +} // namespace io + +namespace util { + +inline ImageCtx *get_image_ctx(librbd::MockReplayImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util + +} // namespace librbd + +// template definitions +#include "librbd/journal/Replay.cc" +template class librbd::journal::Replay; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::StrEq; +using ::testing::WithArgs; + +MATCHER_P(BufferlistEqual, str, "") { + bufferlist bl(arg); + return (strncmp(bl.c_str(), str, strlen(str)) == 0); +} + +MATCHER_P(CStrEq, str, "") { + return (strncmp(arg, str, strlen(str)) == 0); +} + +ACTION_P2(NotifyInvoke, lock, cond) { + std::lock_guard locker{*lock}; + cond->notify_all(); +} + +ACTION_P2(CompleteAioCompletion, r, image_ctx) { + image_ctx->op_work_queue->queue(new LambdaContext([this, arg0](int r) { + arg0->get(); + arg0->init_time(image_ctx, librbd::io::AIO_TYPE_NONE); + arg0->set_request_count(1); + arg0->complete_request(r); + }), r); +} + +namespace librbd { +namespace journal { + +class TestMockJournalReplay : public TestMockFixture { +public: + typedef io::ImageRequest MockIoImageRequest; + typedef Replay MockJournalReplay; + + TestMockJournalReplay() = default; + + void expect_accept_ops(MockExclusiveLock &mock_exclusive_lock, bool accept) { + EXPECT_CALL(mock_exclusive_lock, accept_ops()).WillRepeatedly( + Return(accept)); + } + + void expect_aio_discard(MockIoImageRequest &mock_io_image_request, + io::AioCompletion **aio_comp, uint64_t off, + uint64_t len, uint32_t discard_granularity_bytes) { + EXPECT_CALL(mock_io_image_request, aio_discard(_, io::Extents{{off, len}}, + discard_granularity_bytes)) + .WillOnce(SaveArg<0>(aio_comp)); + } + + void expect_aio_flush(MockIoImageRequest &mock_io_image_request, + io::AioCompletion **aio_comp) { + EXPECT_CALL(mock_io_image_request, aio_flush(_)) + .WillOnce(SaveArg<0>(aio_comp)); + } + + void expect_aio_flush(MockReplayImageCtx &mock_image_ctx, + MockIoImageRequest &mock_io_image_request, int r) { + EXPECT_CALL(mock_io_image_request, aio_flush(_)) + .WillOnce(CompleteAioCompletion(r, mock_image_ctx.image_ctx)); + } + + void expect_aio_write(MockIoImageRequest &mock_io_image_request, + io::AioCompletion **aio_comp, uint64_t off, + uint64_t len, const char *data) { + EXPECT_CALL(mock_io_image_request, + aio_write(_, io::Extents{{off, len}}, BufferlistEqual(data), _)) + .WillOnce(SaveArg<0>(aio_comp)); + } + + void expect_aio_writesame(MockIoImageRequest &mock_io_image_request, + io::AioCompletion **aio_comp, uint64_t off, + uint64_t len, const char *data) { + EXPECT_CALL(mock_io_image_request, + aio_writesame(_, io::Extents{{off, len}}, + BufferlistEqual(data), _)) + .WillOnce(SaveArg<0>(aio_comp)); + } + + void expect_aio_compare_and_write(MockIoImageRequest &mock_io_image_request, + io::AioCompletion **aio_comp, uint64_t off, + uint64_t len, const char *cmp_data, + const char *data, + uint64_t *mismatch_offset) { + EXPECT_CALL(mock_io_image_request, + aio_compare_and_write(_, io::Extents{{off, len}}, + BufferlistEqual(cmp_data), + BufferlistEqual(data), + mismatch_offset, _)) + .WillOnce(SaveArg<0>(aio_comp)); + } + + void expect_flatten(MockReplayImageCtx &mock_image_ctx, Context **on_finish) { + EXPECT_CALL(*mock_image_ctx.operations, execute_flatten(_, _)) + .WillOnce(DoAll(SaveArg<1>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_rename(MockReplayImageCtx &mock_image_ctx, Context **on_finish, + const char *image_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_rename(StrEq(image_name), _)) + .WillOnce(DoAll(SaveArg<1>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_resize(MockReplayImageCtx &mock_image_ctx, Context **on_finish, + uint64_t size, uint64_t op_tid) { + EXPECT_CALL(*mock_image_ctx.operations, execute_resize(size, _, _, _, op_tid)) + .WillOnce(DoAll(SaveArg<3>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_create(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *snap_name, + uint64_t op_tid) { + EXPECT_CALL(*mock_image_ctx.operations, + execute_snap_create(_, StrEq(snap_name), _, op_tid, + SNAP_CREATE_FLAG_SKIP_NOTIFY_QUIESCE, _)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_remove(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *snap_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_remove(_, StrEq(snap_name), _)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_rename(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, uint64_t snap_id, + const char *snap_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_rename(snap_id, StrEq(snap_name), _)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_protect(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *snap_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_protect(_, StrEq(snap_name), _)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_unprotect(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *snap_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_unprotect(_, StrEq(snap_name), _)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_snap_rollback(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *snap_name) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_rollback(_, StrEq(snap_name), _, _)) + .WillOnce(DoAll(SaveArg<3>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_update_features(MockReplayImageCtx &mock_image_ctx, Context **on_finish, + uint64_t features, bool enabled, uint64_t op_tid) { + EXPECT_CALL(*mock_image_ctx.operations, execute_update_features(features, enabled, _, op_tid)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_metadata_set(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *key, + const char *value) { + EXPECT_CALL(*mock_image_ctx.operations, execute_metadata_set(StrEq(key), + StrEq(value), _)) + .WillOnce(DoAll(SaveArg<2>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_metadata_remove(MockReplayImageCtx &mock_image_ctx, + Context **on_finish, const char *key) { + EXPECT_CALL(*mock_image_ctx.operations, execute_metadata_remove(StrEq(key), _)) + .WillOnce(DoAll(SaveArg<1>(on_finish), + NotifyInvoke(&m_invoke_lock, &m_invoke_cond))); + } + + void expect_refresh_image(MockReplayImageCtx &mock_image_ctx, bool required, + int r) { + EXPECT_CALL(*mock_image_ctx.state, is_refresh_required()) + .WillOnce(Return(required)); + if (required) { + EXPECT_CALL(*mock_image_ctx.state, refresh(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + } + + void when_process(MockJournalReplay &mock_journal_replay, + EventEntry &&event_entry, Context *on_ready, + Context *on_safe) { + bufferlist bl; + encode(event_entry, bl); + + auto it = bl.cbegin(); + when_process(mock_journal_replay, &it, on_ready, on_safe); + } + + void when_process(MockJournalReplay &mock_journal_replay, + bufferlist::const_iterator *it, Context *on_ready, + Context *on_safe) { + EventEntry event_entry; + int r = mock_journal_replay.decode(it, &event_entry); + ASSERT_EQ(0, r); + + mock_journal_replay.process(event_entry, on_ready, on_safe); + } + + void when_complete(MockReplayImageCtx &mock_image_ctx, + io::AioCompletion *aio_comp, int r) { + aio_comp->get(); + aio_comp->init_time(mock_image_ctx.image_ctx, librbd::io::AIO_TYPE_NONE); + aio_comp->set_request_count(1); + aio_comp->complete_request(r); + } + + int when_flush(MockJournalReplay &mock_journal_replay) { + C_SaferCond ctx; + mock_journal_replay.flush(&ctx); + return ctx.wait(); + } + + int when_shut_down(MockJournalReplay &mock_journal_replay, bool cancel_ops) { + C_SaferCond ctx; + mock_journal_replay.shut_down(cancel_ops, &ctx); + return ctx.wait(); + } + + void when_replay_op_ready(MockJournalReplay &mock_journal_replay, + uint64_t op_tid, Context *on_resume) { + mock_journal_replay.replay_op_ready(op_tid, on_resume); + } + + void wait_for_op_invoked(Context **on_finish, int r) { + { + std::unique_lock locker{m_invoke_lock}; + m_invoke_cond.wait(locker, [on_finish] { return *on_finish != nullptr; }); + } + (*on_finish)->complete(r); + } + + bufferlist to_bl(const std::string &str) { + bufferlist bl; + bl.append(str); + return bl; + } + + ceph::mutex m_invoke_lock = ceph::make_mutex("m_invoke_lock"); + ceph::condition_variable m_invoke_cond; +}; + +TEST_F(TestMockJournalReplay, AioDiscard) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_discard(mock_io_image_request, &aio_comp, 123, 456, + ictx->discard_granularity_bytes); + when_process(mock_journal_replay, + EventEntry{AioDiscardEvent(123, 456, + ictx->discard_granularity_bytes)}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, AioWrite) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_write(mock_io_image_request, &aio_comp, 123, 456, "test"); + when_process(mock_journal_replay, + EventEntry{AioWriteEvent(123, 456, to_bl("test"))}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, AioFlush) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_flush(mock_io_image_request, &aio_comp); + when_process(mock_journal_replay, EventEntry{AioFlushEvent()}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_safe.wait()); + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + ASSERT_EQ(0, on_ready.wait()); +} + +TEST_F(TestMockJournalReplay, AioWriteSame) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_writesame(mock_io_image_request, &aio_comp, 123, 456, "333"); + when_process(mock_journal_replay, + EventEntry{AioWriteSameEvent(123, 456, to_bl("333"))}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); +} + + +TEST_F(TestMockJournalReplay, AioCompareAndWrite) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_write_journal_replay(mock_image_ctx); + MockJournalReplay mock_compare_and_write_journal_replay(mock_image_ctx); + MockJournalReplay mock_mis_compare_and_write_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_write(mock_io_image_request, &aio_comp, 512, 512, "test"); + when_process(mock_write_journal_replay, + EventEntry{AioWriteEvent(512, 512, to_bl("test"))}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_write_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); + + expect_aio_compare_and_write(mock_io_image_request, &aio_comp, + 512, 512, "test", "test", nullptr); + when_process(mock_compare_and_write_journal_replay, + EventEntry{AioCompareAndWriteEvent(512, 512, to_bl("test"), + to_bl("test"))}, &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_compare_and_write_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); + + expect_aio_compare_and_write(mock_io_image_request, &aio_comp, + 512, 512, "111", "test", nullptr); + when_process(mock_mis_compare_and_write_journal_replay, + EventEntry{AioCompareAndWriteEvent(512, 512, to_bl("111"), + to_bl("test"))}, &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_mis_compare_and_write_journal_replay, false)); + ASSERT_EQ(0, on_safe.wait()); + +} + +TEST_F(TestMockJournalReplay, IOError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_discard(mock_io_image_request, &aio_comp, 123, 456, + ictx->discard_granularity_bytes); + when_process(mock_journal_replay, + EventEntry{AioDiscardEvent(123, 456, + ictx->discard_granularity_bytes)}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, -EINVAL); + ASSERT_EQ(-EINVAL, on_safe.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + ASSERT_EQ(0, on_ready.wait()); +} + +TEST_F(TestMockJournalReplay, SoftFlushIO) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + const size_t io_count = 32; + C_SaferCond on_safes[io_count]; + for (size_t i = 0; i < io_count; ++i) { + io::AioCompletion *aio_comp; + io::AioCompletion *flush_comp = nullptr; + C_SaferCond on_ready; + expect_aio_discard(mock_io_image_request, &aio_comp, 123, 456, + ictx->discard_granularity_bytes); + if (i == io_count - 1) { + expect_aio_flush(mock_io_image_request, &flush_comp); + } + when_process(mock_journal_replay, + EventEntry{AioDiscardEvent(123, 456, + ictx->discard_granularity_bytes)}, + &on_ready, &on_safes[i]); + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + if (flush_comp != nullptr) { + when_complete(mock_image_ctx, flush_comp, 0); + } + } + for (auto &on_safe : on_safes) { + ASSERT_EQ(0, on_safe.wait()); + } + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); +} + +TEST_F(TestMockJournalReplay, PauseIO) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + const size_t io_count = 64; + std::list flush_comps; + C_SaferCond on_safes[io_count]; + for (size_t i = 0; i < io_count; ++i) { + io::AioCompletion *aio_comp; + C_SaferCond on_ready; + expect_aio_write(mock_io_image_request, &aio_comp, 123, 456, "test"); + if ((i + 1) % 32 == 0) { + flush_comps.push_back(nullptr); + expect_aio_flush(mock_io_image_request, &flush_comps.back()); + } + when_process(mock_journal_replay, + EventEntry{AioWriteEvent(123, 456, to_bl("test"))}, + &on_ready, &on_safes[i]); + when_complete(mock_image_ctx, aio_comp, 0); + if (i < io_count - 1) { + ASSERT_EQ(0, on_ready.wait()); + } else { + for (auto flush_comp : flush_comps) { + when_complete(mock_image_ctx, flush_comp, 0); + } + ASSERT_EQ(0, on_ready.wait()); + } + } + for (auto &on_safe : on_safes) { + ASSERT_EQ(0, on_safe.wait()); + } + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); +} + +TEST_F(TestMockJournalReplay, Flush) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + io::AioCompletion *aio_comp = nullptr; + C_SaferCond on_ready; + C_SaferCond on_safe; + expect_aio_discard(mock_io_image_request, &aio_comp, 123, 456, + ictx->discard_granularity_bytes); + when_process(mock_journal_replay, + EventEntry{AioDiscardEvent(123, 456, + ictx->discard_granularity_bytes)}, + &on_ready, &on_safe); + + when_complete(mock_image_ctx, aio_comp, 0); + ASSERT_EQ(0, on_ready.wait()); + + expect_aio_flush(mock_image_ctx, mock_io_image_request, 0); + ASSERT_EQ(0, when_flush(mock_journal_replay)); + ASSERT_EQ(0, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, OpFinishError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, -EIO)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(-EIO, on_start_safe.wait()); + ASSERT_EQ(-EIO, on_finish_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); +} + +TEST_F(TestMockJournalReplay, BlockedOpFinishError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_create(mock_image_ctx, &on_finish, "snap", 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapCreateEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, -EBADMSG)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(-EBADMSG, on_resume.wait()); + wait_for_op_invoked(&on_finish, -ESTALE); + + ASSERT_EQ(-ESTALE, on_start_safe.wait()); + ASSERT_EQ(-ESTALE, on_finish_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); +} + +TEST_F(TestMockJournalReplay, MissingOpFinishEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + EXPECT_CALL(*mock_image_ctx.state, is_refresh_required()) + .WillRepeatedly(Return(false)); + + InSequence seq; + Context *on_snap_create_finish = nullptr; + expect_snap_create(mock_image_ctx, &on_snap_create_finish, "snap", 123); + + Context *on_snap_remove_finish = nullptr; + expect_snap_remove(mock_image_ctx, &on_snap_remove_finish, "snap"); + + C_SaferCond on_snap_remove_ready; + C_SaferCond on_snap_remove_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(122, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_snap_remove_ready, + &on_snap_remove_safe); + ictx->op_work_queue->drain(); + ASSERT_EQ(0, on_snap_remove_ready.wait()); + + C_SaferCond on_snap_create_ready; + C_SaferCond on_snap_create_safe; + when_process(mock_journal_replay, + EventEntry{SnapCreateEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_snap_create_ready, + &on_snap_create_safe); + ictx->op_work_queue->drain(); + + C_SaferCond on_shut_down; + mock_journal_replay.shut_down(false, &on_shut_down); + + wait_for_op_invoked(&on_snap_remove_finish, 0); + ASSERT_EQ(0, on_snap_remove_safe.wait()); + + C_SaferCond on_snap_create_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_snap_create_resume); + ASSERT_EQ(0, on_snap_create_resume.wait()); + + wait_for_op_invoked(&on_snap_create_finish, 0); + ASSERT_EQ(0, on_snap_create_ready.wait()); + ASSERT_EQ(0, on_snap_create_safe.wait()); + + ASSERT_EQ(0, on_shut_down.wait()); +} + +TEST_F(TestMockJournalReplay, MissingOpFinishEventCancelOps) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_snap_create_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_create(mock_image_ctx, &on_snap_create_finish, "snap", 123); + + C_SaferCond on_snap_remove_ready; + C_SaferCond on_snap_remove_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(122, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_snap_remove_ready, + &on_snap_remove_safe); + ictx->op_work_queue->drain(); + ASSERT_EQ(0, on_snap_remove_ready.wait()); + + C_SaferCond on_snap_create_ready; + C_SaferCond on_snap_create_safe; + when_process(mock_journal_replay, + EventEntry{SnapCreateEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_snap_create_ready, + &on_snap_create_safe); + ictx->op_work_queue->drain(); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_snap_create_ready.wait()); + + C_SaferCond on_shut_down; + mock_journal_replay.shut_down(true, &on_shut_down); + + ASSERT_EQ(-ERESTART, on_resume.wait()); + on_snap_create_finish->complete(-ERESTART); + ASSERT_EQ(-ERESTART, on_snap_create_safe.wait()); + + ASSERT_EQ(-ERESTART, on_snap_remove_safe.wait()); + ASSERT_EQ(0, on_shut_down.wait()); +} + +TEST_F(TestMockJournalReplay, UnknownOpFinishEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_ready, &on_safe); + + ASSERT_EQ(0, on_safe.wait()); + ASSERT_EQ(0, on_ready.wait()); +} + +TEST_F(TestMockJournalReplay, OpEventError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_remove(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EINVAL); + ASSERT_EQ(-EINVAL, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(-EINVAL, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapCreateEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_create(mock_image_ctx, &on_finish, "snap", 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapCreateEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_resume.wait()); + wait_for_op_invoked(&on_finish, 0); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapCreateEventExists) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_create(mock_image_ctx, &on_finish, "snap", 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapCreateEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + + wait_for_op_invoked(&on_finish, -EEXIST); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapRemoveEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_remove(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapRemoveEventDNE) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_remove(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRemoveEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -ENOENT); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapRenameEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_rename(mock_image_ctx, &on_finish, 234, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRenameEvent(123, 234, "snap1", "snap")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapRenameEventExists) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_rename(mock_image_ctx, &on_finish, 234, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRenameEvent(123, 234, "snap1", "snap")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EEXIST); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapProtectEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_protect(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapProtectEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapProtectEventBusy) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_protect(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapProtectEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EBUSY); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapUnprotectEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_unprotect(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapUnprotectEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapUnprotectOpFinishBusy) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapUnprotectEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + // aborts the snap unprotect op if image had children + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, -EBUSY)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); +} + +TEST_F(TestMockJournalReplay, SnapUnprotectEventInvalid) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_unprotect(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapUnprotectEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EINVAL); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, SnapRollbackEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_snap_rollback(mock_image_ctx, &on_finish, "snap"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{SnapRollbackEvent(123, + cls::rbd::UserSnapshotNamespace(), + "snap")}, + &on_start_ready, + &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, RenameEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_rename(mock_image_ctx, &on_finish, "image"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{RenameEvent(123, "image")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, RenameEventExists) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_rename(mock_image_ctx, &on_finish, "image"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{RenameEvent(123, "image")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EEXIST); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, ResizeEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_resize(mock_image_ctx, &on_finish, 234, 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{ResizeEvent(123, 234)}, + &on_start_ready, &on_start_safe); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_resume.wait()); + wait_for_op_invoked(&on_finish, 0); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, FlattenEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_flatten(mock_image_ctx, &on_finish); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{FlattenEvent(123)}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, FlattenEventInvalid) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_flatten(mock_image_ctx, &on_finish); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{FlattenEvent(123)}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -EINVAL); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, UpdateFeaturesEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features = RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF; + bool enabled = !ictx->test_features(features); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, false, 0); + expect_update_features(mock_image_ctx, &on_finish, features, enabled, 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, + EventEntry{UpdateFeaturesEvent(123, features, enabled)}, + &on_start_ready, &on_start_safe); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_resume.wait()); + wait_for_op_invoked(&on_finish, 0); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, MetadataSetEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_metadata_set(mock_image_ctx, &on_finish, "key", "value"); + expect_refresh_image(mock_image_ctx, false, 0); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{MetadataSetEvent(123, "key", "value")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, MetadataRemoveEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_metadata_remove(mock_image_ctx, &on_finish, "key"); + expect_refresh_image(mock_image_ctx, false, 0); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{MetadataRemoveEvent(123, "key")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, 0); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, MetadataRemoveEventDNE) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_metadata_remove(mock_image_ctx, &on_finish, "key"); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{MetadataRemoveEvent(123, "key")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + wait_for_op_invoked(&on_finish, -ENOENT); + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, UnknownEvent) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + bufferlist bl; + ENCODE_START(1, 1, bl); + encode(static_cast(-1), bl); + ENCODE_FINISH(bl); + + auto it = bl.cbegin(); + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, &it, &on_ready, &on_safe); + + ASSERT_EQ(0, on_safe.wait()); + ASSERT_EQ(0, on_ready.wait()); +} + +TEST_F(TestMockJournalReplay, RefreshImageBeforeOpStart) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + Context *on_finish = nullptr; + expect_refresh_image(mock_image_ctx, true, 0); + expect_resize(mock_image_ctx, &on_finish, 234, 123); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{ResizeEvent(123, 234)}, + &on_start_ready, &on_start_safe); + + C_SaferCond on_resume; + when_replay_op_ready(mock_journal_replay, 123, &on_resume); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(0, on_resume.wait()); + wait_for_op_invoked(&on_finish, 0); + + ASSERT_EQ(0, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(0, on_finish_safe.wait()); +} + +TEST_F(TestMockJournalReplay, FlushEventAfterShutDown) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, EventEntry{AioFlushEvent()}, + &on_ready, &on_safe); + ASSERT_EQ(0, on_ready.wait()); + ASSERT_EQ(-ESHUTDOWN, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, ModifyEventAfterShutDown) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, + EventEntry{AioWriteEvent(123, 456, to_bl("test"))}, + &on_ready, &on_safe); + ASSERT_EQ(0, on_ready.wait()); + ASSERT_EQ(-ESHUTDOWN, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, OpEventAfterShutDown) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, true); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); + + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, EventEntry{RenameEvent(123, "image")}, + &on_ready, &on_safe); + ASSERT_EQ(0, on_ready.wait()); + ASSERT_EQ(-ESHUTDOWN, on_safe.wait()); +} + +TEST_F(TestMockJournalReplay, LockLostBeforeProcess) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, false); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + MockIoImageRequest mock_io_image_request; + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + C_SaferCond on_ready; + C_SaferCond on_safe; + when_process(mock_journal_replay, EventEntry{AioFlushEvent()}, + &on_ready, &on_safe); + ASSERT_EQ(0, on_ready.wait()); + ASSERT_EQ(-ECANCELED, on_safe.wait()); + + ASSERT_EQ(0, when_shut_down(mock_journal_replay, false)); +} + +TEST_F(TestMockJournalReplay, LockLostBeforeExecuteOp) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockReplayImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_accept_ops(mock_exclusive_lock, false); + + MockJournalReplay mock_journal_replay(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_exclusive_lock, accept_ops()).WillOnce(Return(true)); + EXPECT_CALL(mock_exclusive_lock, accept_ops()).WillOnce(Return(true)); + expect_refresh_image(mock_image_ctx, false, 0); + + C_SaferCond on_start_ready; + C_SaferCond on_start_safe; + when_process(mock_journal_replay, EventEntry{RenameEvent(123, "image")}, + &on_start_ready, &on_start_safe); + ASSERT_EQ(0, on_start_ready.wait()); + + C_SaferCond on_finish_ready; + C_SaferCond on_finish_safe; + when_process(mock_journal_replay, EventEntry{OpFinishEvent(123, 0)}, + &on_finish_ready, &on_finish_safe); + + ASSERT_EQ(-ECANCELED, on_start_safe.wait()); + ASSERT_EQ(0, on_finish_ready.wait()); + ASSERT_EQ(-ECANCELED, on_finish_safe.wait()); +} + +} // namespace journal +} // namespace librbd diff --git a/src/test/librbd/journal/test_mock_ResetRequest.cc b/src/test/librbd/journal/test_mock_ResetRequest.cc new file mode 100644 index 000000000..2c722558c --- /dev/null +++ b/src/test/librbd/journal/test_mock_ResetRequest.cc @@ -0,0 +1,278 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/journal/mock/MockJournaler.h" +#include "cls/journal/cls_journal_types.h" +#include "librbd/journal/CreateRequest.h" +#include "librbd/journal/RemoveRequest.h" +#include "librbd/journal/ResetRequest.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace journal { + +template <> +struct TypeTraits { + typedef ::journal::MockJournalerProxy Journaler; +}; + +template <> +struct CreateRequest { + static CreateRequest* s_instance; + Context* on_finish = nullptr; + + static CreateRequest* create(IoCtx &ioctx, const std::string &imageid, + uint8_t order, uint8_t splay_width, + const std::string &object_pool, + uint64_t tag_class, TagData &tag_data, + const std::string &client_id, + ContextWQ *op_work_queue, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + CreateRequest() { + s_instance = this; + } +}; + +template <> +struct RemoveRequest { + static RemoveRequest* s_instance; + Context* on_finish = nullptr; + + static RemoveRequest* create(IoCtx &ioctx, const std::string &image_id, + const std::string &client_id, + ContextWQ *op_work_queue, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + RemoveRequest() { + s_instance = this; + } +}; + +CreateRequest* CreateRequest::s_instance = nullptr; +RemoveRequest* RemoveRequest::s_instance = nullptr; + +} // namespace journal +} // namespace librbd + +#include "librbd/journal/ResetRequest.cc" + +namespace librbd { +namespace journal { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; + +class TestMockJournalResetRequest : public TestMockFixture { +public: + typedef ResetRequest MockResetRequest; + typedef CreateRequest MockCreateRequest; + typedef RemoveRequest MockRemoveRequest; + + void expect_construct_journaler(::journal::MockJournaler &mock_journaler) { + EXPECT_CALL(mock_journaler, construct()); + } + + void expect_init_journaler(::journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, init(_)) + .WillOnce(CompleteContext(r, static_cast(NULL))); + } + + void expect_get_metadata(::journal::MockJournaler& mock_journaler) { + EXPECT_CALL(mock_journaler, get_metadata(_, _, _)) + .WillOnce(Invoke([](uint8_t* order, uint8_t* splay_width, + int64_t* pool_id) { + *order = 24; + *splay_width = 4; + *pool_id = -1; + })); + } + + void expect_shut_down_journaler(::journal::MockJournaler &mock_journaler, + int r) { + EXPECT_CALL(mock_journaler, shut_down(_)) + .WillOnce(CompleteContext(r, static_cast(NULL))); + } + + void expect_remove(MockRemoveRequest& mock_remove_request, int r) { + EXPECT_CALL(mock_remove_request, send()) + .WillOnce(Invoke([&mock_remove_request, r]() { + mock_remove_request.on_finish->complete(r); + })); + } + + void expect_create(MockCreateRequest& mock_create_request, int r) { + EXPECT_CALL(mock_create_request, send()) + .WillOnce(Invoke([&mock_create_request, r]() { + mock_create_request.on_finish->complete(r); + })); + } +}; + +TEST_F(TestMockJournalResetRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + InSequence seq; + ::journal::MockJournaler mock_journaler; + expect_construct_journaler(mock_journaler); + expect_init_journaler(mock_journaler, 0); + expect_get_metadata(mock_journaler); + expect_shut_down_journaler(mock_journaler, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + ContextWQ* context_wq; + Journal<>::get_work_queue(ictx->cct, &context_wq); + + C_SaferCond ctx; + auto req = MockResetRequest::create(m_ioctx, "image id", + Journal<>::IMAGE_CLIENT_ID, + Journal<>::LOCAL_MIRROR_UUID, + context_wq, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockJournalResetRequest, InitError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + InSequence seq; + ::journal::MockJournaler mock_journaler; + expect_construct_journaler(mock_journaler); + expect_init_journaler(mock_journaler, -EINVAL); + expect_shut_down_journaler(mock_journaler, 0); + + ContextWQ* context_wq; + Journal<>::get_work_queue(ictx->cct, &context_wq); + + C_SaferCond ctx; + auto req = MockResetRequest::create(m_ioctx, "image id", + Journal<>::IMAGE_CLIENT_ID, + Journal<>::LOCAL_MIRROR_UUID, + context_wq, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockJournalResetRequest, ShutDownError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + InSequence seq; + ::journal::MockJournaler mock_journaler; + expect_construct_journaler(mock_journaler); + expect_init_journaler(mock_journaler, 0); + expect_get_metadata(mock_journaler); + expect_shut_down_journaler(mock_journaler, -EINVAL); + + ContextWQ* context_wq; + Journal<>::get_work_queue(ictx->cct, &context_wq); + + C_SaferCond ctx; + auto req = MockResetRequest::create(m_ioctx, "image id", + Journal<>::IMAGE_CLIENT_ID, + Journal<>::LOCAL_MIRROR_UUID, + context_wq, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockJournalResetRequest, RemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + InSequence seq; + ::journal::MockJournaler mock_journaler; + expect_construct_journaler(mock_journaler); + expect_init_journaler(mock_journaler, 0); + expect_get_metadata(mock_journaler); + expect_shut_down_journaler(mock_journaler, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, -EINVAL); + + ContextWQ* context_wq; + Journal<>::get_work_queue(ictx->cct, &context_wq); + + C_SaferCond ctx; + auto req = MockResetRequest::create(m_ioctx, "image id", + Journal<>::IMAGE_CLIENT_ID, + Journal<>::LOCAL_MIRROR_UUID, + context_wq, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockJournalResetRequest, CreateError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + InSequence seq; + ::journal::MockJournaler mock_journaler; + expect_construct_journaler(mock_journaler); + expect_init_journaler(mock_journaler, 0); + expect_get_metadata(mock_journaler); + expect_shut_down_journaler(mock_journaler, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, -EINVAL); + + ContextWQ* context_wq; + Journal<>::get_work_queue(ictx->cct, &context_wq); + + C_SaferCond ctx; + auto req = MockResetRequest::create(m_ioctx, "image id", + Journal<>::IMAGE_CLIENT_ID, + Journal<>::LOCAL_MIRROR_UUID, + context_wq, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace journal +} // namespace librbd -- cgit v1.2.3