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