]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: support replay of maintenance ops
authorJason Dillaman <dillaman@redhat.com>
Wed, 24 Feb 2016 20:51:08 +0000 (15:51 -0500)
committerJason Dillaman <dillaman@redhat.com>
Tue, 1 Mar 2016 12:38:05 +0000 (07:38 -0500)
Ignore errors that are to be expected when replaying
a maintenance op two or more times.

Fixes: #14822
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/librbd/journal/Replay.cc
src/librbd/journal/Replay.h
src/test/librbd/journal/test_mock_Replay.cc

index 52c6e73618223a305066e95c5f490d9eb8321468..4b6daf19ab280558d46c02876f61da85d42bb2ae 100644 (file)
@@ -248,6 +248,7 @@ void Replay<I>::handle_event(const journal::OpFinishEvent &event,
                  << "op_tid=" << event.op_tid << dendl;
 
   bool op_in_progress;
+  Context *on_op_complete = nullptr;
   Context *on_op_finish_event = nullptr;
   {
     Mutex::Locker locker(m_lock);
@@ -265,6 +266,7 @@ void Replay<I>::handle_event(const journal::OpFinishEvent &event,
     op_event.on_finish_ready = on_ready;
     op_event.on_finish_safe = on_safe;
     op_in_progress = op_event.op_in_progress;
+    std::swap(on_op_complete, op_event.on_op_complete);
     std::swap(on_op_finish_event, op_event.on_op_finish_event);
   }
 
@@ -273,9 +275,13 @@ void Replay<I>::handle_event(const journal::OpFinishEvent &event,
       // bubble the error up to the in-progress op to cancel it
       on_op_finish_event->complete(event.r);
     } else {
-      // op hasn't been started -- no-op the event
+      // op hasn't been started -- bubble the error up since
+      // our image is now potentially in an inconsistent state
+      // since simple errors should have been caught before
+      // creating the op event
+      delete on_op_complete;
       delete on_op_finish_event;
-      handle_op_complete(event.op_tid, 0);
+      handle_op_complete(event.op_tid, event.r);
     }
     return;
   }
@@ -295,6 +301,9 @@ void Replay<I>::handle_event(const journal::SnapCreateEvent &event,
   Context *on_op_complete = create_op_context_callback(event.op_tid, on_safe,
                                                        &op_event);
 
+  // ignore errors caused due to replay
+  op_event->ignore_error_codes = {-EEXIST};
+
   // avoid lock cycles
   m_image_ctx.op_work_queue->queue(
     new ExecuteOp<I, journal::SnapCreateEvent>(m_image_ctx, event,
@@ -322,6 +331,9 @@ void Replay<I>::handle_event(const journal::SnapRemoveEvent &event,
                                           on_op_complete);
     });
 
+  // ignore errors caused due to replay
+  op_event->ignore_error_codes = {-ENOENT};
+
   on_ready->complete(0);
 }
 
@@ -342,6 +354,9 @@ void Replay<I>::handle_event(const journal::SnapRenameEvent &event,
                                           on_op_complete);
     });
 
+  // ignore errors caused due to replay
+  op_event->ignore_error_codes = {-EEXIST};
+
   on_ready->complete(0);
 }
 
@@ -361,6 +376,9 @@ void Replay<I>::handle_event(const journal::SnapProtectEvent &event,
                                            on_op_complete);
     });
 
+  // ignore errors caused due to replay
+  op_event->ignore_error_codes = {-EBUSY};
+
   on_ready->complete(0);
 }
 
@@ -381,6 +399,9 @@ void Replay<I>::handle_event(const journal::SnapUnprotectEvent &event,
                                              on_op_complete);
     });
 
+  // ignore errors caused due to replay
+  op_event->ignore_error_codes = {-EINVAL};
+
   on_ready->complete(0);
 }
 
@@ -420,6 +441,9 @@ void Replay<I>::handle_event(const journal::RenameEvent &event,
       m_image_ctx.operations->rename(event.image_name.c_str(), on_op_complete);
     });
 
+  // ignore errors caused due to replay
+  op_event->ignore_error_codes = {-EEXIST};
+
   on_ready->complete(0);
 }
 
@@ -460,6 +484,9 @@ void Replay<I>::handle_event(const journal::FlattenEvent &event,
       m_image_ctx.operations->flatten(no_op_progress_callback, on_op_complete);
     });
 
+  // ignore errors caused due to replay
+  op_event->ignore_error_codes = {-EINVAL};
+
   on_ready->complete(0);
 }
 
@@ -583,9 +610,18 @@ void Replay<I>::handle_op_complete(uint64_t op_tid, int r) {
     }
   }
 
-  assert(op_event.on_start_ready == nullptr);
-  assert((op_event.on_finish_ready != nullptr &&
-          op_event.on_finish_safe != nullptr) || r == -ERESTART);
+  assert(op_event.on_start_ready == nullptr || (r < 0 && r != -ERESTART));
+  if (op_event.on_start_ready != nullptr) {
+    // blocking op event failed before it became ready
+    assert(op_event.on_finish_ready == nullptr &&
+           op_event.on_finish_safe == nullptr);
+
+    op_event.on_start_ready->complete(0);
+  } else {
+    // event kicked off by OpFinishEvent
+    assert((op_event.on_finish_ready != nullptr &&
+            op_event.on_finish_safe != nullptr) || r == -ERESTART);
+  }
 
   // skipped upon error -- so clean up if non-null
   delete op_event.on_op_finish_event;
@@ -597,6 +633,11 @@ void Replay<I>::handle_op_complete(uint64_t op_tid, int r) {
     op_event.on_finish_ready->complete(0);
   }
 
+  // filter out errors caused by replay of the same op
+  if (r < 0 && op_event.ignore_error_codes.count(r) != 0) {
+    r = 0;
+  }
+
   op_event.on_start_safe->complete(r);
   if (op_event.on_finish_safe != nullptr) {
     op_event.on_finish_safe->complete(r);
index fdd1df790590873b9bbffd13ba0c3aaba7511134..d461090092e3738b8114c6ded8ffeb7713cd7c4c 100644 (file)
@@ -41,6 +41,8 @@ public:
   void replay_op_ready(uint64_t op_tid, Context *on_resume);
 
 private:
+  typedef std::unordered_set<int> ReturnValues;
+
   struct OpEvent {
     bool op_in_progress = false;
     Context *on_op_finish_event = nullptr;
@@ -49,6 +51,7 @@ private:
     Context *on_finish_ready = nullptr;
     Context *on_finish_safe = nullptr;
     Context *on_op_complete = nullptr;
+    ReturnValues ignore_error_codes;
   };
 
   typedef std::list<uint64_t> OpTids;
index 43c35396431d1eaff7f8c01f57b254d628e94b7f..9ec20ffc72878d37f0344a1d3dd4c34c582b20b9 100644 (file)
@@ -62,6 +62,11 @@ MATCHER_P(CStrEq, str, "") {
   return (strncmp(arg, str, strlen(str)) == 0);
 }
 
+ACTION_P2(NotifyInvoke, lock, cond) {
+  Mutex::Locker locker(*lock);
+  cond->Signal();
+}
+
 ACTION_P2(CompleteAioCompletion, r, image_ctx) {
   CephContext *cct = image_ctx->cct;
   image_ctx->op_work_queue->queue(new FunctionContext([cct, arg0](int r) {
@@ -79,6 +84,9 @@ public:
   typedef AioImageRequest<MockImageCtx> MockAioImageRequest;
   typedef Replay<MockImageCtx> MockJournalReplay;
 
+  TestMockJournalReplay() : m_invoke_lock("m_invoke_lock") {
+  }
+
   void expect_aio_discard(MockAioImageRequest &mock_aio_image_request,
                           AioCompletion **aio_comp, uint64_t off,
                           uint64_t len) {
@@ -108,19 +116,22 @@ public:
 
   void expect_flatten(MockImageCtx &mock_image_ctx, Context **on_finish) {
     EXPECT_CALL(*mock_image_ctx.operations, flatten(_, _))
-                  .WillOnce(SaveArg<1>(on_finish));
+                  .WillOnce(DoAll(SaveArg<1>(on_finish),
+                                  NotifyInvoke(&m_invoke_lock, &m_invoke_cond)));
   }
 
   void expect_rename(MockImageCtx &mock_image_ctx, Context **on_finish,
                      const char *image_name) {
     EXPECT_CALL(*mock_image_ctx.operations, rename(CStrEq(image_name), _))
-                  .WillOnce(SaveArg<1>(on_finish));
+                  .WillOnce(DoAll(SaveArg<1>(on_finish),
+                                  NotifyInvoke(&m_invoke_lock, &m_invoke_cond)));
   }
 
   void expect_resize(MockImageCtx &mock_image_ctx, Context **on_finish,
                      uint64_t size, uint64_t op_tid) {
     EXPECT_CALL(*mock_image_ctx.operations, resize(size, _, _, op_tid))
-                  .WillOnce(SaveArg<2>(on_finish));
+                  .WillOnce(DoAll(SaveArg<2>(on_finish),
+                                  NotifyInvoke(&m_invoke_lock, &m_invoke_cond)));
   }
 
   void expect_snap_create(MockImageCtx &mock_image_ctx,
@@ -128,38 +139,44 @@ public:
                           uint64_t op_tid) {
     EXPECT_CALL(*mock_image_ctx.operations, snap_create(CStrEq(snap_name), _,
                                                         op_tid))
-                  .WillOnce(SaveArg<1>(on_finish));
+                  .WillOnce(DoAll(SaveArg<1>(on_finish),
+                                  NotifyInvoke(&m_invoke_lock, &m_invoke_cond)));
   }
 
   void expect_snap_remove(MockImageCtx &mock_image_ctx,
                           Context **on_finish, const char *snap_name) {
     EXPECT_CALL(*mock_image_ctx.operations, snap_remove(CStrEq(snap_name), _))
-                  .WillOnce(SaveArg<1>(on_finish));
+                  .WillOnce(DoAll(SaveArg<1>(on_finish),
+                                  NotifyInvoke(&m_invoke_lock, &m_invoke_cond)));
   }
 
   void expect_snap_rename(MockImageCtx &mock_image_ctx,
                           Context **on_finish, uint64_t snap_id,
                           const char *snap_name) {
     EXPECT_CALL(*mock_image_ctx.operations, snap_rename(snap_id, CStrEq(snap_name), _))
-                  .WillOnce(SaveArg<2>(on_finish));
+                  .WillOnce(DoAll(SaveArg<2>(on_finish),
+                                  NotifyInvoke(&m_invoke_lock, &m_invoke_cond)));
   }
 
   void expect_snap_protect(MockImageCtx &mock_image_ctx,
                            Context **on_finish, const char *snap_name) {
     EXPECT_CALL(*mock_image_ctx.operations, snap_protect(CStrEq(snap_name), _))
-                  .WillOnce(SaveArg<1>(on_finish));
+                  .WillOnce(DoAll(SaveArg<1>(on_finish),
+                                  NotifyInvoke(&m_invoke_lock, &m_invoke_cond)));
   }
 
   void expect_snap_unprotect(MockImageCtx &mock_image_ctx,
                              Context **on_finish, const char *snap_name) {
     EXPECT_CALL(*mock_image_ctx.operations, snap_unprotect(CStrEq(snap_name), _))
-                  .WillOnce(SaveArg<1>(on_finish));
+                  .WillOnce(DoAll(SaveArg<1>(on_finish),
+                                  NotifyInvoke(&m_invoke_lock, &m_invoke_cond)));
   }
 
   void expect_snap_rollback(MockImageCtx &mock_image_ctx,
                             Context **on_finish, const char *snap_name) {
     EXPECT_CALL(*mock_image_ctx.operations, snap_rollback(CStrEq(snap_name), _, _))
-                  .WillOnce(SaveArg<2>(on_finish));
+                  .WillOnce(DoAll(SaveArg<2>(on_finish),
+                                  NotifyInvoke(&m_invoke_lock, &m_invoke_cond)));
   }
 
   void when_process(MockJournalReplay &mock_journal_replay,
@@ -202,11 +219,24 @@ public:
     mock_journal_replay.replay_op_ready(op_tid, on_resume);
   }
 
+  void wait_for_op_invoked(Context **on_finish, int r) {
+    {
+      Mutex::Locker locker(m_invoke_lock);
+      while (*on_finish == nullptr) {
+        m_invoke_cond.Wait(m_invoke_lock);
+      }
+    }
+    (*on_finish)->complete(r);
+  }
+
   bufferlist to_bl(const std::string &str) {
     bufferlist bl;
     bl.append(str);
     return bl;
   }
+
+  Mutex m_invoke_lock;
+  Cond m_invoke_cond;
 };
 
 TEST_F(TestMockJournalReplay, AioDiscard) {
@@ -427,6 +457,69 @@ TEST_F(TestMockJournalReplay, Flush) {
   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));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  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, "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));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournalReplay mock_journal_replay(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  Context *on_finish;
+  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, "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());
+  on_finish->complete(-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) {
   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));
@@ -447,10 +540,6 @@ TEST_F(TestMockJournalReplay, MissingOpFinishEvent) {
   ASSERT_EQ(-ERESTART, on_safe.wait());
 }
 
-TEST_F(TestMockJournalReplay, MissingCompleteOpFinishEvent) {
-  // TODO
-}
-
 TEST_F(TestMockJournalReplay, UnknownOpFinishEvent) {
   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));
@@ -536,6 +625,38 @@ TEST_F(TestMockJournalReplay, SnapCreateEvent) {
   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));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournalReplay mock_journal_replay(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  Context *on_finish = nullptr;
+  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, "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);
 
@@ -567,6 +688,37 @@ TEST_F(TestMockJournalReplay, SnapRemoveEvent) {
   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));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournalReplay mock_journal_replay(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  Context *on_finish;
+  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, "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);
+
+  on_finish->complete(-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);
 
@@ -599,6 +751,38 @@ TEST_F(TestMockJournalReplay, SnapRenameEvent) {
   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));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournalReplay mock_journal_replay(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  Context *on_finish;
+  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, "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);
+
+  on_finish->complete(-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);
 
@@ -630,6 +814,37 @@ TEST_F(TestMockJournalReplay, SnapProtectEvent) {
   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));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournalReplay mock_journal_replay(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  Context *on_finish;
+  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, "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);
+
+  on_finish->complete(-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);
 
@@ -661,6 +876,37 @@ TEST_F(TestMockJournalReplay, SnapUnprotectEvent) {
   ASSERT_EQ(0, on_finish_safe.wait());
 }
 
+TEST_F(TestMockJournalReplay, SnapUnprotectEventInvalid) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournalReplay mock_journal_replay(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  Context *on_finish;
+  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, "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);
+
+  on_finish->complete(-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);
 
@@ -723,6 +969,37 @@ TEST_F(TestMockJournalReplay, RenameEvent) {
   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));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournalReplay mock_journal_replay(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  Context *on_finish;
+  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);
+
+  on_finish->complete(-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);
 
@@ -790,6 +1067,37 @@ TEST_F(TestMockJournalReplay, FlattenEvent) {
   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));
+
+  MockImageCtx mock_image_ctx(*ictx);
+  MockJournalReplay mock_journal_replay(mock_image_ctx);
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  Context *on_finish;
+  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);
+
+  on_finish->complete(-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, UnknownEvent) {
   REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);