// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include "librbd/crypto/CryptoObjectDispatch.h" #include "librbd/crypto/Utils.h" #include "test/librbd/test_mock_fixture.h" #include "test/librbd/test_support.h" #include "test/librbd/mock/MockImageCtx.h" #include "test/librbd/mock/crypto/MockCryptoInterface.h" #include "test/librbd/mock/crypto/MockEncryptionFormat.h" #include "test/librbd/mock/io/MockObjectDispatch.h" namespace librbd { namespace { struct MockTestImageCtx : public MockImageCtx { MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { } MockTestImageCtx *parent = nullptr; }; } // anonymous namespace namespace util { inline ImageCtx *get_image_ctx(MockTestImageCtx *image_ctx) { return image_ctx->image_ctx; } } // namespace util namespace crypto { namespace util { template <> void set_crypto(MockTestImageCtx *image_ctx, std::unique_ptr encryption_format) { image_ctx->encryption_format = std::move(encryption_format); } } // namespace util } // namespace crypto } // namespace librbd #include "librbd/crypto/LoadRequest.cc" namespace librbd { namespace crypto { using ::testing::_; using ::testing::Invoke; using ::testing::Return; using ::testing::WithArgs; struct TestMockCryptoLoadRequest : public TestMockFixture { typedef LoadRequest MockLoadRequest; MockTestImageCtx* mock_image_ctx; MockTestImageCtx* mock_parent_image_ctx; C_SaferCond finished_cond; Context *on_finish = &finished_cond; MockEncryptionFormat* mock_encryption_format; MockEncryptionFormat* cloned_encryption_format; Context* load_context; MockLoadRequest* mock_load_request; void SetUp() override { TestMockFixture::SetUp(); librbd::ImageCtx *ictx; ASSERT_EQ(0, open_image(m_image_name, &ictx)); mock_image_ctx = new MockTestImageCtx(*ictx); mock_parent_image_ctx = new MockTestImageCtx(*ictx); mock_image_ctx->parent = mock_parent_image_ctx; mock_encryption_format = new MockEncryptionFormat(); std::vector> formats; formats.emplace_back(mock_encryption_format); mock_load_request = MockLoadRequest::create( mock_image_ctx, std::move(formats), on_finish); } void TearDown() override { delete mock_image_ctx; delete mock_parent_image_ctx; TestMockFixture::TearDown(); } void expect_test_journal_feature(MockTestImageCtx* ictx, bool has_journal = false) { EXPECT_CALL(*ictx, test_features( RBD_FEATURE_JOURNALING)).WillOnce(Return(has_journal)); } void expect_encryption_load(MockEncryptionFormat* encryption_format, MockTestImageCtx* ictx, std::string detected_format = "SOMEFORMAT") { EXPECT_CALL(*encryption_format, load( ictx, _, _)).WillOnce( WithArgs<1, 2>(Invoke([this, detected_format]( std::string* detected_format_name, Context* ctx) { if (!detected_format.empty()) { *detected_format_name = detected_format; } load_context = ctx; }))); } void expect_encryption_format_clone(MockEncryptionFormat* encryption_format) { cloned_encryption_format = new MockEncryptionFormat(); EXPECT_CALL(*encryption_format, clone()).WillOnce( Invoke([this]() { return std::unique_ptr( cloned_encryption_format); })); } void expect_image_flush(int r = 0) { EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_)).WillOnce( Invoke([r](io::ImageDispatchSpec* spec) { ASSERT_TRUE(boost::get( &spec->request) != nullptr); spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; spec->aio_comp->set_request_count(1); spec->aio_comp->add_request(); spec->aio_comp->complete_request(r); })); } void expect_invalidate_cache(int r = 0) { EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, invalidate_cache(_)).WillOnce( Invoke([r](Context* ctx) { ctx->complete(r); })); } }; TEST_F(TestMockCryptoLoadRequest, NoFormats) { delete mock_load_request; std::vector> formats; mock_load_request = MockLoadRequest::create( mock_image_ctx, std::move(formats), on_finish); mock_load_request->send(); ASSERT_EQ(-EINVAL, finished_cond.wait()); } TEST_F(TestMockCryptoLoadRequest, CryptoAlreadyLoaded) { mock_image_ctx->encryption_format.reset(new MockEncryptionFormat()); mock_load_request->send(); ASSERT_EQ(-EEXIST, finished_cond.wait()); } TEST_F(TestMockCryptoLoadRequest, JournalEnabled) { expect_test_journal_feature(mock_image_ctx, true); mock_load_request->send(); ASSERT_EQ(-ENOTSUP, finished_cond.wait()); } TEST_F(TestMockCryptoLoadRequest, JournalEnabledOnParent) { expect_test_journal_feature(mock_image_ctx); expect_test_journal_feature(mock_parent_image_ctx, true); mock_load_request->send(); ASSERT_EQ(-ENOTSUP, finished_cond.wait()); } TEST_F(TestMockCryptoLoadRequest, LoadFail) { expect_test_journal_feature(mock_image_ctx); expect_test_journal_feature(mock_parent_image_ctx); expect_image_flush(); expect_encryption_load(mock_encryption_format, mock_image_ctx); mock_load_request->send(); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); load_context->complete(-EINVAL); ASSERT_EQ(-EINVAL, finished_cond.wait()); } TEST_F(TestMockCryptoLoadRequest, Success) { delete mock_load_request; mock_image_ctx->parent = nullptr; mock_encryption_format = new MockEncryptionFormat(); std::vector> formats; formats.emplace_back(mock_encryption_format); mock_load_request = MockLoadRequest::create( mock_image_ctx, std::move(formats), on_finish); expect_test_journal_feature(mock_image_ctx); expect_image_flush(); expect_encryption_load(mock_encryption_format, mock_image_ctx); mock_load_request->send(); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); expect_invalidate_cache(); load_context->complete(0); ASSERT_EQ(0, finished_cond.wait()); ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get()); } TEST_F(TestMockCryptoLoadRequest, FlushFail) { delete mock_load_request; mock_image_ctx->parent = nullptr; mock_encryption_format = new MockEncryptionFormat(); std::vector> formats; formats.emplace_back(mock_encryption_format); mock_load_request = MockLoadRequest::create( mock_image_ctx, std::move(formats), on_finish); expect_test_journal_feature(mock_image_ctx); expect_image_flush(-EIO); mock_load_request->send(); ASSERT_EQ(-EIO, finished_cond.wait()); } TEST_F(TestMockCryptoLoadRequest, InvalidateCacheFail) { delete mock_load_request; mock_image_ctx->parent = nullptr; mock_encryption_format = new MockEncryptionFormat(); std::vector> formats; formats.emplace_back(mock_encryption_format); mock_load_request = MockLoadRequest::create( mock_image_ctx, std::move(formats), on_finish); expect_test_journal_feature(mock_image_ctx); expect_image_flush(); expect_encryption_load(mock_encryption_format, mock_image_ctx); mock_load_request->send(); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); expect_invalidate_cache(-EIO); load_context->complete(0); ASSERT_EQ(-EIO, finished_cond.wait()); } TEST_F(TestMockCryptoLoadRequest, LoadClonedEncryptedParent) { expect_test_journal_feature(mock_image_ctx); expect_test_journal_feature(mock_parent_image_ctx); expect_image_flush(); expect_encryption_load(mock_encryption_format, mock_image_ctx); mock_load_request->send(); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); expect_encryption_format_clone(mock_encryption_format); expect_encryption_load(cloned_encryption_format, mock_parent_image_ctx); load_context->complete(0); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); expect_invalidate_cache(); load_context->complete(0); ASSERT_EQ(0, finished_cond.wait()); ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get()); ASSERT_EQ(cloned_encryption_format, mock_parent_image_ctx->encryption_format.get()); } TEST_F(TestMockCryptoLoadRequest, LoadClonedParentFail) { expect_test_journal_feature(mock_image_ctx); expect_test_journal_feature(mock_parent_image_ctx); expect_image_flush(); expect_encryption_load(mock_encryption_format, mock_image_ctx); mock_load_request->send(); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); expect_encryption_format_clone(mock_encryption_format); expect_encryption_load(cloned_encryption_format, mock_parent_image_ctx); load_context->complete(0); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); load_context->complete(-EIO); ASSERT_EQ(-EIO, finished_cond.wait()); ASSERT_EQ(nullptr, mock_image_ctx->encryption_format.get()); ASSERT_EQ(nullptr, mock_parent_image_ctx->encryption_format.get()); } TEST_F(TestMockCryptoLoadRequest, LoadClonedPlaintextParent) { expect_test_journal_feature(mock_image_ctx); expect_test_journal_feature(mock_parent_image_ctx); expect_image_flush(); expect_encryption_load(mock_encryption_format, mock_image_ctx); mock_load_request->send(); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); expect_encryption_format_clone(mock_encryption_format); expect_encryption_load( cloned_encryption_format, mock_parent_image_ctx, LoadRequest::UNKNOWN_FORMAT); load_context->complete(0); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); expect_invalidate_cache(); load_context->complete(-EINVAL); ASSERT_EQ(0, finished_cond.wait()); ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get()); ASSERT_EQ(nullptr, mock_parent_image_ctx->encryption_format.get()); } TEST_F(TestMockCryptoLoadRequest, LoadClonedParentDetectionError) { expect_test_journal_feature(mock_image_ctx); expect_test_journal_feature(mock_parent_image_ctx); expect_image_flush(); expect_encryption_load(mock_encryption_format, mock_image_ctx); mock_load_request->send(); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); expect_encryption_format_clone(mock_encryption_format); expect_encryption_load( cloned_encryption_format, mock_parent_image_ctx, ""); load_context->complete(0); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); load_context->complete(-EINVAL); ASSERT_EQ(-EINVAL, finished_cond.wait()); ASSERT_EQ(nullptr, mock_image_ctx->encryption_format.get()); ASSERT_EQ(nullptr, mock_parent_image_ctx->encryption_format.get()); } TEST_F(TestMockCryptoLoadRequest, LoadParentFail) { delete mock_load_request; mock_encryption_format = new MockEncryptionFormat(); auto mock_parent_encryption_format = new MockEncryptionFormat(); std::vector> formats; formats.emplace_back(mock_encryption_format); formats.emplace_back(mock_parent_encryption_format); mock_load_request = MockLoadRequest::create( mock_image_ctx, std::move(formats), on_finish); expect_test_journal_feature(mock_image_ctx); expect_test_journal_feature(mock_parent_image_ctx); expect_image_flush(); expect_encryption_load(mock_encryption_format, mock_image_ctx); mock_load_request->send(); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); expect_encryption_load(mock_parent_encryption_format, mock_parent_image_ctx); load_context->complete(0); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); load_context->complete(-EINVAL); ASSERT_EQ(-EINVAL, finished_cond.wait()); ASSERT_EQ(nullptr, mock_image_ctx->encryption_format.get()); ASSERT_EQ(nullptr, mock_parent_image_ctx->encryption_format.get()); } TEST_F(TestMockCryptoLoadRequest, EncryptedParent) { delete mock_load_request; mock_encryption_format = new MockEncryptionFormat(); auto mock_parent_encryption_format = new MockEncryptionFormat(); std::vector> formats; formats.emplace_back(mock_encryption_format); formats.emplace_back(mock_parent_encryption_format); mock_load_request = MockLoadRequest::create( mock_image_ctx, std::move(formats), on_finish); expect_test_journal_feature(mock_image_ctx); expect_test_journal_feature(mock_parent_image_ctx); expect_image_flush(); expect_encryption_load(mock_encryption_format, mock_image_ctx); mock_load_request->send(); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); expect_encryption_load(mock_parent_encryption_format, mock_parent_image_ctx); load_context->complete(0); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); expect_invalidate_cache(); load_context->complete(0); ASSERT_EQ(0, finished_cond.wait()); ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get()); ASSERT_EQ(mock_parent_encryption_format, mock_parent_image_ctx->encryption_format.get()); } TEST_F(TestMockCryptoLoadRequest, TooManyFormats) { delete mock_load_request; mock_encryption_format = new MockEncryptionFormat(); auto mock_parent_encryption_format = new MockEncryptionFormat(); std::vector> formats; formats.emplace_back(mock_encryption_format); formats.emplace_back(mock_parent_encryption_format); mock_image_ctx->parent = nullptr; mock_load_request = MockLoadRequest::create( mock_image_ctx, std::move(formats), on_finish); expect_test_journal_feature(mock_image_ctx); expect_image_flush(); expect_encryption_load(mock_encryption_format, mock_image_ctx); mock_load_request->send(); ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); load_context->complete(0); ASSERT_EQ(-EINVAL, finished_cond.wait()); ASSERT_EQ(nullptr, mock_image_ctx->encryption_format.get()); } } // namespace crypto } // namespace librbd