]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
tests: unit tests for librbd Journal
authorJason Dillaman <dillaman@redhat.com>
Tue, 15 Dec 2015 21:16:53 +0000 (16:16 -0500)
committerJason Dillaman <dillaman@redhat.com>
Fri, 15 Jan 2016 15:40:28 +0000 (10:40 -0500)
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/test/Makefile-client.am
src/test/librbd/mock/MockImageCtx.h
src/test/librbd/test_mock_Journal.cc [new file with mode: 0644]

index a2c47186b5d51ecc1f51c378bdc1015c3af12ed7..cdcadff7a3d3812cdda62bc7f6d2a532c0bf14b3 100644 (file)
@@ -362,6 +362,7 @@ unittest_librbd_SOURCES = \
         test/librbd/test_main.cc \
        test/librbd/test_mock_fixture.cc \
        test/librbd/test_mock_ExclusiveLock.cc \
+       test/librbd/test_mock_Journal.cc \
        test/librbd/exclusive_lock/test_mock_AcquireRequest.cc \
        test/librbd/exclusive_lock/test_mock_ReleaseRequest.cc \
        test/librbd/object_map/test_mock_InvalidateRequest.cc \
index c19692094e3d27b9dda562e0eb3f45ba38a62504..f5af5d2164d6596ddc13de589f99cf255428d3fa 100644 (file)
@@ -22,6 +22,7 @@ struct MockImageCtx {
   MockImageCtx(librbd::ImageCtx &image_ctx)
     : image_ctx(&image_ctx),
       cct(image_ctx.cct),
+      snap_id(image_ctx.snap_id),
       snapc(image_ctx.snapc),
       snaps(image_ctx.snaps),
       snap_info(image_ctx.snap_info),
@@ -45,7 +46,14 @@ struct MockImageCtx {
       exclusive_lock(NULL), journal(NULL),
       concurrent_management_ops(image_ctx.concurrent_management_ops),
       blacklist_on_break_lock(image_ctx.blacklist_on_break_lock),
-      blacklist_expire_seconds(image_ctx.blacklist_expire_seconds)
+      blacklist_expire_seconds(image_ctx.blacklist_expire_seconds),
+      journal_order(image_ctx.journal_order),
+      journal_splay_width(image_ctx.journal_splay_width),
+      journal_commit_age(image_ctx.journal_commit_age),
+      journal_object_flush_interval(image_ctx.journal_object_flush_interval),
+      journal_object_flush_bytes(image_ctx.journal_object_flush_bytes),
+      journal_object_flush_age(image_ctx.journal_object_flush_age),
+      journal_pool(image_ctx.journal_pool)
   {
     md_ctx.dup(image_ctx.md_ctx);
     data_ctx.dup(image_ctx.data_ctx);
@@ -110,6 +118,7 @@ struct MockImageCtx {
   ImageCtx *image_ctx;
   CephContext *cct;
 
+  uint64_t snap_id;
   ::SnapContext snapc;
   std::vector<librados::snap_t> snaps;
   std::map<librados::snap_t, SnapInfo> snap_info;
@@ -139,6 +148,7 @@ struct MockImageCtx {
   xlist<AsyncRequest<MockImageCtx>*> async_requests;
   std::list<Context*> async_requests_waiters;
 
+
   MockAioImageRequestWQ *aio_work_queue;
   MockContextWQ *op_work_queue;
 
@@ -154,6 +164,13 @@ struct MockImageCtx {
   int concurrent_management_ops;
   bool blacklist_on_break_lock;
   uint32_t blacklist_expire_seconds;
+  uint8_t journal_order;
+  uint8_t journal_splay_width;
+  double journal_commit_age;
+  int journal_object_flush_interval;
+  uint64_t journal_object_flush_bytes;
+  double journal_object_flush_age;
+  std::string journal_pool;
 };
 
 } // namespace librbd
diff --git a/src/test/librbd/test_mock_Journal.cc b/src/test/librbd/test_mock_Journal.cc
new file mode 100644 (file)
index 0000000..0250e5e
--- /dev/null
@@ -0,0 +1,589 @@
+// -*- 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/Journal.h"
+#include "librbd/journal/Replay.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <functional>
+#include <list>
+
+namespace journal {
+
+struct MockFuture {
+  static MockFuture *s_instance;
+  static MockFuture &get_instance() {
+    assert(s_instance != nullptr);
+    return *s_instance;
+  }
+
+  MockFuture() {
+    s_instance = this;
+  }
+
+  MOCK_CONST_METHOD0(is_valid, bool());
+  MOCK_METHOD1(flush, void(Context *));
+  MOCK_METHOD1(wait, void(Context *));
+};
+
+struct MockFutureProxy {
+  bool is_valid() const {
+    return MockFuture::get_instance().is_valid();
+  }
+
+  void flush(Context *on_safe) {
+    MockFuture::get_instance().flush(on_safe);
+  }
+
+  void wait(Context *on_safe) {
+    MockFuture::get_instance().wait(on_safe);
+  }
+};
+
+struct MockReplayEntry {
+  static MockReplayEntry *s_instance;
+  static MockReplayEntry &get_instance() {
+    assert(s_instance != nullptr);
+    return *s_instance;
+  }
+
+  MockReplayEntry() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(get_data, bufferlist());
+};
+
+struct MockReplayEntryProxy {
+  bufferlist get_data() {
+    return MockReplayEntry::get_instance().get_data();
+  }
+};
+
+struct MockJournaler {
+  static MockJournaler *s_instance;
+  static MockJournaler &get_instance() {
+    assert(s_instance != nullptr);
+    return *s_instance;
+  }
+
+  MockJournaler() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(construct, void());
+
+  MOCK_METHOD3(get_metadata, void(uint8_t *order, uint8_t *splay_width,
+                                  int64_t *pool_id));
+  MOCK_METHOD1(init, void(Context*));
+
+  MOCK_METHOD1(start_replay, void(::journal::ReplayHandler *replay_handler));
+  MOCK_METHOD1(try_pop_front, bool(MockReplayEntryProxy *replay_entry));
+  MOCK_METHOD0(stop_replay, void());
+
+  MOCK_METHOD3(start_append, void(int flush_interval, uint64_t flush_bytes,
+                                  double flush_age));
+  MOCK_METHOD2(append, MockFutureProxy(const std::string &tag,
+                                       const bufferlist &bl));
+  MOCK_METHOD1(flush, void(Context *on_safe));
+  MOCK_METHOD1(stop_append, void(Context *on_safe));
+
+  MOCK_METHOD1(committed, void(const MockReplayEntryProxy &replay_entry));
+  MOCK_METHOD1(committed, void(const MockFutureProxy &future));
+};
+
+struct MockJournalerProxy {
+  template <typename IoCtxT>
+  MockJournalerProxy(IoCtxT &header_ioctx, const std::string &,
+                     const std::string &, double) {
+    MockJournaler::get_instance().construct();
+  }
+
+  int exists(bool *header_exists) const {
+    return -EINVAL;
+  }
+  int create(uint8_t order, uint8_t splay_width, int64_t pool_id) {
+    return -EINVAL;
+  }
+  int remove(bool force) {
+    return -EINVAL;
+  }
+  int register_client(const std::string &description) {
+    return -EINVAL;
+  }
+
+  void get_metadata(uint8_t *order, uint8_t *splay_width, int64_t *pool_id) {
+    MockJournaler::get_instance().get_metadata(order, splay_width, pool_id);
+  }
+
+  void init(Context *on_finish) {
+    MockJournaler::get_instance().init(on_finish);
+  }
+
+  void start_replay(::journal::ReplayHandler *replay_handler) {
+    MockJournaler::get_instance().start_replay(replay_handler);
+  }
+
+  bool try_pop_front(MockReplayEntryProxy *replay_entry) {
+    return MockJournaler::get_instance().try_pop_front(replay_entry);
+  }
+
+  void stop_replay() {
+    MockJournaler::get_instance().stop_replay();
+  }
+
+  void start_append(int flush_interval, uint64_t flush_bytes, double flush_age) {
+    MockJournaler::get_instance().start_append(flush_interval, flush_bytes,
+                                               flush_age);
+  }
+
+  MockFutureProxy append(const std::string &tag, const bufferlist &bl) {
+    return MockJournaler::get_instance().append(tag, bl);
+  }
+
+  void flush(Context *on_safe) {
+    MockJournaler::get_instance().flush(on_safe);
+  }
+
+  void stop_append(Context *on_safe) {
+    MockJournaler::get_instance().stop_append(on_safe);
+  }
+
+  void committed(const MockReplayEntryProxy &replay_entry) {
+    MockJournaler::get_instance().committed(replay_entry);
+  }
+
+  void committed(const MockFutureProxy &future) {
+    MockJournaler::get_instance().committed(future);
+  }
+};
+
+MockFuture *MockFuture::s_instance = nullptr;
+MockReplayEntry *MockReplayEntry::s_instance = nullptr;
+MockJournaler *MockJournaler::s_instance = nullptr;
+
+} // namespace journal
+
+namespace librbd {
+namespace journal {
+
+template <>
+struct TypeTraits<MockImageCtx> {
+  typedef ::journal::MockJournalerProxy Journaler;
+  typedef ::journal::MockFutureProxy  Future;
+  typedef ::journal::MockReplayEntryProxy ReplayEntry;
+};
+
+struct MockReplay {
+  static MockReplay *s_instance;
+  static MockReplay &get_instance() {
+    assert(s_instance != nullptr);
+    return *s_instance;
+  }
+
+  MockReplay() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD1(flush, void(Context *));
+  MOCK_METHOD2(process, int(bufferlist::iterator, Context *));
+};
+
+template <>
+class Replay<MockImageCtx> {
+public:
+  static Replay *create(MockImageCtx &image_ctx) {
+    return new Replay();
+  }
+
+  void flush(Context *on_finish) {
+    MockReplay::get_instance().flush(on_finish);
+  }
+
+  int process(bufferlist::iterator it, Context *on_commit) {
+    return MockReplay::get_instance().process(it, on_commit);
+  }
+};
+
+MockReplay *MockReplay::s_instance = nullptr;
+
+} // namespace journal
+} // namespace librbd
+
+// template definitions
+#include "librbd/Journal.cc"
+template class librbd::Journal<librbd::MockImageCtx>;
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::MatcherCast;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::WithArg;
+using namespace std::placeholders;
+
+ACTION_P2(StartReplay, wq, ctx) {
+  ctx->replay_handler = arg0;
+  wq->queue(ctx, 0);
+}
+
+namespace librbd {
+
+class TestMockJournal : public TestMockFixture {
+public:
+  typedef journal::MockReplay MockJournalReplay;
+  typedef Journal<MockImageCtx> MockJournal;
+
+  typedef std::function<void(::journal::ReplayHandler*)> ReplayAction;
+  typedef std::list<ReplayAction> ReplayActions;
+  typedef std::list<Context *> Contexts;
+
+  Contexts m_commit_contexts;
+  struct C_StartReplay : public Context {
+    ReplayActions replay_actions;
+    ::journal::ReplayHandler *replay_handler;
+
+    C_StartReplay(const ReplayActions &replay_actions)
+      : replay_actions(replay_actions), replay_handler(nullptr) {
+    }
+    virtual void finish(int r) {
+      for (auto &action : replay_actions) {
+        action(replay_handler);
+      }
+    }
+  };
+
+  ~TestMockJournal() {
+    assert(m_commit_contexts.empty());
+  }
+
+  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, NULL));
+
+  }
+
+  void expect_start_replay(MockImageCtx &mock_image_ctx,
+                           ::journal::MockJournaler &mock_journaler,
+                           const ReplayActions &actions) {
+    EXPECT_CALL(mock_journaler, start_replay(_))
+                 .WillOnce(StartReplay(mock_image_ctx.image_ctx->op_work_queue,
+                                       new C_StartReplay(actions)));
+  }
+
+  void expect_stop_replay(::journal::MockJournaler &mock_journaler) {
+    EXPECT_CALL(mock_journaler, stop_replay());
+  }
+
+  void expect_flush_replay(MockJournalReplay &mock_journal_replay, int r) {
+    EXPECT_CALL(mock_journal_replay, flush(_))
+                  .WillOnce(CompleteContext(r, NULL));
+  }
+
+  void expect_get_data(::journal::MockReplayEntry &mock_replay_entry) {
+    EXPECT_CALL(mock_replay_entry, get_data())
+                  .WillOnce(Return(bufferlist()));
+  }
+
+  void expect_try_pop_front(::journal::MockJournaler &mock_journaler,
+                            bool entries_available,
+                            ::journal::MockReplayEntry &mock_replay_entry) {
+    EXPECT_CALL(mock_journaler, try_pop_front(_))
+                  .WillOnce(DoAll(SetArgPointee<0>(::journal::MockReplayEntryProxy()),
+                                  Return(entries_available)));
+    if (entries_available) {
+      expect_get_data(mock_replay_entry);
+    }
+  }
+
+  void expect_replay_process(MockJournalReplay &mock_journal_replay, int r) {
+    auto &expect = EXPECT_CALL(mock_journal_replay, process(_, _));
+    if (r < 0) {
+      expect.WillOnce(Return(r));
+    } else {
+      expect.WillOnce(DoAll(WithArg<1>(Invoke(this, &TestMockJournal::save_commit_context)),
+                            Return(0)));
+    }
+  }
+
+  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, NULL));
+  }
+
+  void expect_committed(::journal::MockJournaler &mock_journaler) {
+    EXPECT_CALL(mock_journaler, committed(MatcherCast<const ::journal::MockReplayEntryProxy&>(_)))
+                  .Times(m_commit_contexts.size());
+  }
+
+  int when_open(MockJournal &mock_journal) {
+    C_SaferCond ctx;
+    mock_journal.open(&ctx);
+    return ctx.wait();
+  }
+
+  void when_commit_replay(int r) {
+    for (auto ctx : m_commit_contexts) {
+      ctx->complete(r);
+    }
+    m_commit_contexts.clear();
+  }
+
+  int when_close(MockJournal &mock_journal) {
+    C_SaferCond ctx;
+    mock_journal.close(&ctx);
+    return ctx.wait();
+  }
+
+  void save_commit_context(Context *ctx) {
+    m_commit_contexts.push_back(ctx);
+  }
+
+  static void invoke_replay_ready(::journal::ReplayHandler *handler) {
+    handler->handle_entries_available();
+  }
+
+  static void invoke_replay_complete(::journal::ReplayHandler *handler, int r) {
+    handler->handle_complete(r);
+  }
+};
+
+TEST_F(TestMockJournal, StateTransitions) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+
+  ::journal::MockJournaler mock_journaler;
+  expect_construct_journaler(mock_journaler);
+  expect_init_journaler(mock_journaler, 0);
+  expect_start_replay(
+    mock_image_ctx, mock_journaler, {
+      std::bind(&invoke_replay_ready, _1),
+      std::bind(&invoke_replay_ready, _1),
+      std::bind(&invoke_replay_complete, _1, 0)
+    });
+
+  ::journal::MockReplayEntry mock_replay_entry;
+  MockJournalReplay mock_journal_replay;
+  expect_try_pop_front(mock_journaler, true, mock_replay_entry);
+  expect_replay_process(mock_journal_replay, 0);
+  expect_try_pop_front(mock_journaler, true, mock_replay_entry);
+  expect_replay_process(mock_journal_replay, 0);
+  expect_try_pop_front(mock_journaler, false, mock_replay_entry);
+  expect_try_pop_front(mock_journaler, true, mock_replay_entry);
+  expect_replay_process(mock_journal_replay, 0);
+  expect_try_pop_front(mock_journaler, false, mock_replay_entry);
+
+  expect_stop_replay(mock_journaler);
+  expect_flush_replay(mock_journal_replay, 0);
+
+  expect_start_append(mock_journaler);
+
+  ASSERT_EQ(0, when_open(mock_journal));
+
+  expect_committed(mock_journaler);
+  when_commit_replay(0);
+
+  expect_stop_append(mock_journaler, 0);
+  ASSERT_EQ(0, when_close(mock_journal));
+}
+
+TEST_F(TestMockJournal, InitError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+
+  ::journal::MockJournaler mock_journaler;
+  expect_construct_journaler(mock_journaler);
+  expect_init_journaler(mock_journaler, -EINVAL);
+  ASSERT_EQ(-EINVAL, when_open(mock_journal));
+}
+
+TEST_F(TestMockJournal, ReplayProcessError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+
+  ::journal::MockJournaler mock_journaler;
+  expect_construct_journaler(mock_journaler);
+  expect_init_journaler(mock_journaler, 0);
+  expect_start_replay(
+    mock_image_ctx, mock_journaler, {
+      std::bind(&invoke_replay_ready, _1)
+    });
+
+  ::journal::MockReplayEntry mock_replay_entry;
+  MockJournalReplay mock_journal_replay;
+  expect_try_pop_front(mock_journaler, true, mock_replay_entry);
+  expect_replay_process(mock_journal_replay, -EINVAL);
+
+  expect_stop_replay(mock_journaler);
+  expect_flush_replay(mock_journal_replay, 0);
+
+  // replay failure should result in replay-restart
+  expect_construct_journaler(mock_journaler);
+  expect_init_journaler(mock_journaler, 0);
+  expect_start_replay(
+    mock_image_ctx, mock_journaler, {
+      std::bind(&invoke_replay_complete, _1, 0)
+    });
+
+  expect_stop_replay(mock_journaler);
+  expect_flush_replay(mock_journal_replay, 0);
+  expect_start_append(mock_journaler);
+  ASSERT_EQ(0, when_open(mock_journal));
+
+  expect_stop_append(mock_journaler, 0);
+  ASSERT_EQ(0, when_close(mock_journal));
+}
+
+TEST_F(TestMockJournal, ReplayCompleteError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+
+  ::journal::MockJournaler mock_journaler;
+  expect_construct_journaler(mock_journaler);
+  expect_init_journaler(mock_journaler, 0);
+  expect_start_replay(
+    mock_image_ctx, mock_journaler, {
+      std::bind(&invoke_replay_complete, _1, -EINVAL)
+    });
+
+  MockJournalReplay mock_journal_replay;
+  expect_stop_replay(mock_journaler);
+  expect_flush_replay(mock_journal_replay, 0);
+
+  // replay failure should result in replay-restart
+  expect_construct_journaler(mock_journaler);
+  expect_init_journaler(mock_journaler, 0);
+  expect_start_replay(
+    mock_image_ctx, mock_journaler, {
+      std::bind(&invoke_replay_complete, _1, 0)
+    });
+
+  expect_stop_replay(mock_journaler);
+  expect_flush_replay(mock_journal_replay, 0);
+  expect_start_append(mock_journaler);
+  ASSERT_EQ(0, when_open(mock_journal));
+
+  expect_stop_append(mock_journaler, 0);
+  ASSERT_EQ(0, when_close(mock_journal));
+}
+
+TEST_F(TestMockJournal, FlushReplayError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+
+  ::journal::MockJournaler mock_journaler;
+  expect_construct_journaler(mock_journaler);
+  expect_init_journaler(mock_journaler, 0);
+  expect_start_replay(
+    mock_image_ctx, mock_journaler, {
+      std::bind(&invoke_replay_complete, _1, 0)
+    });
+
+  MockJournalReplay mock_journal_replay;
+  expect_stop_replay(mock_journaler);
+  expect_flush_replay(mock_journal_replay, -EINVAL);
+
+  // replay flush failure should result in replay-restart
+  expect_construct_journaler(mock_journaler);
+  expect_init_journaler(mock_journaler, 0);
+  expect_start_replay(
+    mock_image_ctx, mock_journaler, {
+      std::bind(&invoke_replay_complete, _1, 0)
+    });
+
+  expect_stop_replay(mock_journaler);
+  expect_flush_replay(mock_journal_replay, 0);
+  expect_start_append(mock_journaler);
+  ASSERT_EQ(0, when_open(mock_journal));
+
+  expect_stop_append(mock_journaler, 0);
+  ASSERT_EQ(0, when_close(mock_journal));
+}
+
+TEST_F(TestMockJournal, StopError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+
+  ::journal::MockJournaler mock_journaler;
+  expect_construct_journaler(mock_journaler);
+  expect_init_journaler(mock_journaler, 0);
+  expect_start_replay(
+    mock_image_ctx, mock_journaler, {
+      std::bind(&invoke_replay_complete, _1, 0)
+    });
+
+  MockJournalReplay mock_journal_replay;
+  expect_stop_replay(mock_journaler);
+  expect_flush_replay(mock_journal_replay, 0);
+  expect_start_append(mock_journaler);
+  ASSERT_EQ(0, when_open(mock_journal));
+
+  expect_stop_append(mock_journaler, -EINVAL);
+  ASSERT_EQ(-EINVAL, when_close(mock_journal));
+}
+
+
+
+
+} // namespace librbd