From: Jason Dillaman Date: Wed, 24 Feb 2016 20:51:08 +0000 (-0500) Subject: librbd: support replay of maintenance ops X-Git-Tag: v10.1.0~257^2~4 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=ed9de5fc5e443021aa03a2e5b63d32b4ba31c484;p=ceph.git librbd: support replay of maintenance ops Ignore errors that are to be expected when replaying a maintenance op two or more times. Fixes: #14822 Signed-off-by: Jason Dillaman --- diff --git a/src/librbd/journal/Replay.cc b/src/librbd/journal/Replay.cc index 52c6e7361822..4b6daf19ab28 100644 --- a/src/librbd/journal/Replay.cc +++ b/src/librbd/journal/Replay.cc @@ -248,6 +248,7 @@ void Replay::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::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::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::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(m_image_ctx, event, @@ -322,6 +331,9 @@ void Replay::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::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::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::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::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::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::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::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); diff --git a/src/librbd/journal/Replay.h b/src/librbd/journal/Replay.h index fdd1df790590..d461090092e3 100644 --- a/src/librbd/journal/Replay.h +++ b/src/librbd/journal/Replay.h @@ -41,6 +41,8 @@ public: void replay_op_ready(uint64_t op_tid, Context *on_resume); private: + typedef std::unordered_set 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 OpTids; diff --git a/src/test/librbd/journal/test_mock_Replay.cc b/src/test/librbd/journal/test_mock_Replay.cc index 43c35396431d..9ec20ffc7287 100644 --- a/src/test/librbd/journal/test_mock_Replay.cc +++ b/src/test/librbd/journal/test_mock_Replay.cc @@ -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 MockAioImageRequest; typedef Replay 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);