diff options
Diffstat (limited to 'src/test/librbd/crypto')
-rw-r--r-- | src/test/librbd/crypto/luks/test_mock_FormatRequest.cc | 195 | ||||
-rw-r--r-- | src/test/librbd/crypto/luks/test_mock_LoadRequest.cc | 221 | ||||
-rw-r--r-- | src/test/librbd/crypto/openssl/test_DataCryptor.cc | 118 | ||||
-rw-r--r-- | src/test/librbd/crypto/test_mock_BlockCrypto.cc | 156 | ||||
-rw-r--r-- | src/test/librbd/crypto/test_mock_CryptoContextPool.cc | 54 | ||||
-rw-r--r-- | src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc | 798 | ||||
-rw-r--r-- | src/test/librbd/crypto/test_mock_FormatRequest.cc | 195 | ||||
-rw-r--r-- | src/test/librbd/crypto/test_mock_LoadRequest.cc | 125 | ||||
-rw-r--r-- | src/test/librbd/crypto/test_mock_ShutDownCryptoRequest.cc | 144 |
9 files changed, 2006 insertions, 0 deletions
diff --git a/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc b/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc new file mode 100644 index 000000000..d27c3fe12 --- /dev/null +++ b/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc @@ -0,0 +1,195 @@ +// -*- 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" + +namespace librbd { +namespace util { + +inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util +} // namespace librbd + +#include "librbd/crypto/luks/FormatRequest.cc" + +namespace librbd { +namespace crypto { +namespace luks { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; + +struct TestMockCryptoLuksFormatRequest : public TestMockFixture { + typedef FormatRequest<librbd::MockImageCtx> MockFormatRequest; + + const size_t OBJECT_SIZE = 4 * 1024 * 1024; + const size_t IMAGE_SIZE = 1024 * 1024 * 1024; + const char* passphrase_cstr = "password"; + std::string passphrase = passphrase_cstr; + + MockImageCtx* mock_image_ctx; + C_SaferCond finished_cond; + Context *on_finish = &finished_cond; + io::AioCompletion* aio_comp; + ceph::bufferlist header_bl; + ceph::ref_t<CryptoInterface> crypto; + + void SetUp() override { + TestMockFixture::SetUp(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + mock_image_ctx = new MockImageCtx(*ictx); + crypto = nullptr; + } + + void TearDown() override { + if (crypto != nullptr) { + crypto->put(); + crypto = nullptr; + } + delete mock_image_ctx; + TestMockFixture::TearDown(); + } + + void expect_get_object_size() { + EXPECT_CALL(*mock_image_ctx, get_object_size()).WillOnce(Return( + OBJECT_SIZE)); + } + + void expect_get_image_size(uint64_t image_size) { + EXPECT_CALL(*mock_image_ctx, get_image_size(CEPH_NOSNAP)).WillOnce(Return( + image_size)); + } + + void expect_image_write() { + EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_)) + .WillOnce(Invoke([this](io::ImageDispatchSpec* spec) { + auto* write = boost::get<io::ImageDispatchSpec::Write>( + &spec->request); + ASSERT_TRUE(write != nullptr); + + ASSERT_EQ(1, spec->image_extents.size()); + ASSERT_EQ(0, spec->image_extents[0].first); + ASSERT_GT(spec->image_extents[0].second, 0); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + aio_comp = spec->aio_comp; + header_bl = write->bl; + })); + } + + void complete_aio(int r) { + if (r < 0) { + aio_comp->fail(r); + } else { + aio_comp->set_request_count(1); + aio_comp->add_request(); + aio_comp->complete_request(r); + } + } + + void verify_header(const char* expected_format, size_t expected_key_length, + uint64_t expected_sector_size) { + Header header(mock_image_ctx->cct); + + ASSERT_EQ(0, header.init()); + ASSERT_EQ(0, header.write(header_bl)); + ASSERT_EQ(0, header.load(expected_format)); + + ASSERT_EQ(expected_sector_size, header.get_sector_size()); + ASSERT_EQ(0, header.get_data_offset() % OBJECT_SIZE); + + char volume_key[64]; + size_t volume_key_size = sizeof(volume_key); + ASSERT_EQ(0, header.read_volume_key( + passphrase_cstr, strlen(passphrase_cstr), + reinterpret_cast<char*>(volume_key), &volume_key_size)); + + ASSERT_EQ(expected_key_length, crypto->get_key_length()); + ASSERT_EQ(0, std::memcmp( + volume_key, crypto->get_key(), expected_key_length)); + ASSERT_EQ(expected_sector_size, crypto->get_block_size()); + ASSERT_EQ(header.get_data_offset(), crypto->get_data_offset()); + } +}; + +TEST_F(TestMockCryptoLuksFormatRequest, LUKS1) { + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1, + RBD_ENCRYPTION_ALGORITHM_AES128, std::move(passphrase), &crypto, + on_finish, true); + expect_get_object_size(); + expect_get_image_size(IMAGE_SIZE); + expect_image_write(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + complete_aio(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS1, 32, 512)); +} + +TEST_F(TestMockCryptoLuksFormatRequest, AES128) { + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, + RBD_ENCRYPTION_ALGORITHM_AES128, std::move(passphrase), &crypto, + on_finish, true); + expect_get_object_size(); + expect_get_image_size(IMAGE_SIZE); + expect_image_write(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + complete_aio(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS2, 32, 4096)); +} + +TEST_F(TestMockCryptoLuksFormatRequest, AES256) { + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, + RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto, + on_finish, true); + expect_get_object_size(); + expect_get_image_size(IMAGE_SIZE); + expect_image_write(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + complete_aio(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS2, 64, 4096)); +} + +TEST_F(TestMockCryptoLuksFormatRequest, ImageTooSmall) { + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, + RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto, + on_finish, true); + expect_get_object_size(); + expect_get_image_size(1024*1024); + mock_format_request->send(); + ASSERT_EQ(-ENOSPC, finished_cond.wait()); +} + +TEST_F(TestMockCryptoLuksFormatRequest, WriteFail) { + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, + RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto, + on_finish, true); + expect_get_object_size(); + expect_get_image_size(IMAGE_SIZE); + expect_image_write(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + complete_aio(-123); + ASSERT_EQ(-123, finished_cond.wait()); +} + +} // namespace luks +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc b/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc new file mode 100644 index 000000000..1a2300616 --- /dev/null +++ b/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc @@ -0,0 +1,221 @@ +// -*- 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" + +namespace librbd { +namespace util { + +inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util +} // namespace librbd + +#include "librbd/crypto/luks/LoadRequest.cc" + +namespace librbd { +namespace crypto { +namespace luks { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; + +struct TestMockCryptoLuksLoadRequest : public TestMockFixture { + typedef LoadRequest<librbd::MockImageCtx> MockLoadRequest; + + const size_t OBJECT_SIZE = 4 * 1024 * 1024; + const char* passphrase_cstr = "password"; + std::string passphrase = passphrase_cstr; + + MockImageCtx* mock_image_ctx; + ceph::ref_t<CryptoInterface> crypto; + MockLoadRequest* mock_load_request; + C_SaferCond finished_cond; + Context *on_finish = &finished_cond; + Context* image_read_request; + ceph::bufferlist header_bl; + uint64_t data_offset; + + void SetUp() override { + TestMockFixture::SetUp(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + mock_image_ctx = new MockImageCtx(*ictx); + crypto = nullptr; + mock_load_request = MockLoadRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, std::move(passphrase), + &crypto, on_finish); + } + + void TearDown() override { + delete mock_image_ctx; + if (crypto != nullptr) { + crypto->put(); + crypto = nullptr; + } + TestMockFixture::TearDown(); + } + + // returns data offset in bytes + void generate_header(const char* type, const char* alg, size_t key_size, + const char* cipher_mode, uint32_t sector_size) { + Header header(mock_image_ctx->cct); + + ASSERT_EQ(0, header.init()); + ASSERT_EQ(0, header.format(type, alg, nullptr, key_size, cipher_mode, + sector_size, OBJECT_SIZE, true)); + ASSERT_EQ(0, header.add_keyslot(passphrase_cstr, strlen(passphrase_cstr))); + ASSERT_LE(0, header.read(&header_bl)); + + data_offset = header.get_data_offset(); + } + + void expect_image_read(uint64_t offset, uint64_t length) { + EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_)) + .WillOnce(Invoke([this, offset, + length](io::ImageDispatchSpec* spec) { + auto* read = boost::get<io::ImageDispatchSpec::Read>( + &spec->request); + ASSERT_TRUE(read != nullptr); + + ASSERT_EQ(1, spec->image_extents.size()); + ASSERT_EQ(offset, spec->image_extents[0].first); + ASSERT_EQ(length, spec->image_extents[0].second); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + auto aio_comp = spec->aio_comp; + aio_comp->set_request_count(1); + aio_comp->read_result = std::move(read->read_result); + aio_comp->read_result.set_image_extents(spec->image_extents); + auto ctx = new io::ReadResult::C_ImageReadRequest( + aio_comp, 0, spec->image_extents); + if (header_bl.length() < offset + length) { + header_bl.append_zero(offset + length - header_bl.length()); + } + ctx->bl.substr_of(header_bl, offset, length); + image_read_request = ctx; + })); + } +}; + +TEST_F(TestMockCryptoLuksLoadRequest, AES128) { + generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096); + expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); + mock_load_request->send(); + image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NE(crypto, nullptr); +} + +TEST_F(TestMockCryptoLuksLoadRequest, AES256) { + generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096); + expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); + mock_load_request->send(); + image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NE(crypto, nullptr); +} + +TEST_F(TestMockCryptoLuksLoadRequest, LUKS1) { + delete mock_load_request; + mock_load_request = MockLoadRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1, {passphrase_cstr}, + &crypto, on_finish); + generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512); + expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); + mock_load_request->send(); + image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NE(crypto, nullptr); +} + +TEST_F(TestMockCryptoLuksLoadRequest, WrongFormat) { + generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512); + expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); + mock_load_request->send(); + + expect_image_read(DEFAULT_INITIAL_READ_SIZE, + MAXIMUM_HEADER_SIZE - DEFAULT_INITIAL_READ_SIZE); + image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); // complete 1st read + + image_read_request->complete( + MAXIMUM_HEADER_SIZE - DEFAULT_INITIAL_READ_SIZE); + ASSERT_EQ(-EINVAL, finished_cond.wait()); + ASSERT_EQ(crypto, nullptr); +} + +TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedAlgorithm) { + generate_header(CRYPT_LUKS2, "twofish", 32, "xts-plain64", 4096); + expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); + mock_load_request->send(); + image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); + ASSERT_EQ(-ENOTSUP, finished_cond.wait()); + ASSERT_EQ(crypto, nullptr); +} + +TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedCipherMode) { + generate_header(CRYPT_LUKS2, "aes", 32, "cbc-essiv:sha256", 4096); + expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); + mock_load_request->send(); + image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); + ASSERT_EQ(-ENOTSUP, finished_cond.wait()); + ASSERT_EQ(crypto, nullptr); +} + +TEST_F(TestMockCryptoLuksLoadRequest, HeaderBiggerThanInitialRead) { + generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096); + mock_load_request->set_initial_read_size(4096); + expect_image_read(0, 4096); + mock_load_request->send(); + + expect_image_read(4096, MAXIMUM_HEADER_SIZE - 4096); + image_read_request->complete(4096); // complete initial read + + image_read_request->complete(MAXIMUM_HEADER_SIZE - 4096); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NE(crypto, nullptr); +} + +TEST_F(TestMockCryptoLuksLoadRequest, KeyslotsBiggerThanInitialRead) { + generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096); + mock_load_request->set_initial_read_size(16384); + expect_image_read(0, 16384); + mock_load_request->send(); + + expect_image_read(16384, data_offset - 16384); + image_read_request->complete(16384); // complete initial read + + image_read_request->complete(data_offset - 16384); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NE(crypto, nullptr); +} + +TEST_F(TestMockCryptoLuksLoadRequest, WrongPassphrase) { + delete mock_load_request; + mock_load_request = MockLoadRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, "wrong", &crypto, + on_finish); + + generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096); + expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); + mock_load_request->send(); + + // crypt_volume_key_get will fail, we will retry reading more + expect_image_read(DEFAULT_INITIAL_READ_SIZE, + data_offset - DEFAULT_INITIAL_READ_SIZE); + image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); + + image_read_request->complete(data_offset - DEFAULT_INITIAL_READ_SIZE); + ASSERT_EQ(-EPERM, finished_cond.wait()); + ASSERT_EQ(crypto, nullptr); +} + +} // namespace luks +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/crypto/openssl/test_DataCryptor.cc b/src/test/librbd/crypto/openssl/test_DataCryptor.cc new file mode 100644 index 000000000..a3ba4c883 --- /dev/null +++ b/src/test/librbd/crypto/openssl/test_DataCryptor.cc @@ -0,0 +1,118 @@ +// -*- 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 "librbd/crypto/openssl/DataCryptor.h" + +namespace librbd { +namespace crypto { +namespace openssl { + +const char* TEST_CIPHER_NAME = "aes-256-xts"; +const unsigned char TEST_KEY[64] = {1}; +const unsigned char TEST_IV[16] = {2}; +const unsigned char TEST_IV_2[16] = {3}; +const unsigned char TEST_DATA[4096] = {4}; + +struct TestCryptoOpensslDataCryptor : public TestFixture { + DataCryptor *cryptor; + + void SetUp() override { + TestFixture::SetUp(); + cryptor = new DataCryptor(reinterpret_cast<CephContext*>(m_ioctx.cct())); + ASSERT_EQ(0, + cryptor->init(TEST_CIPHER_NAME, TEST_KEY, sizeof(TEST_KEY))); + } + + void TearDown() override { + delete cryptor; + TestFixture::TearDown(); + } +}; + +TEST_F(TestCryptoOpensslDataCryptor, InvalidCipherName) { + EXPECT_EQ(-EINVAL, cryptor->init(nullptr, TEST_KEY, sizeof(TEST_KEY))); + EXPECT_EQ(-EINVAL, cryptor->init("", TEST_KEY, sizeof(TEST_KEY))); + EXPECT_EQ(-EINVAL, cryptor->init("Invalid", TEST_KEY, sizeof(TEST_KEY))); +} + +TEST_F(TestCryptoOpensslDataCryptor, InvalidKey) { + EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, nullptr, 0)); + EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, nullptr, + sizeof(TEST_KEY))); + EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, TEST_KEY, 1)); +} + +TEST_F(TestCryptoOpensslDataCryptor, GetContextInvalidMode) { + EXPECT_EQ(nullptr, cryptor->get_context(static_cast<CipherMode>(-1))); +} + +TEST_F(TestCryptoOpensslDataCryptor, ReturnNullContext) { + cryptor->return_context(nullptr, static_cast<CipherMode>(-1)); +} + +TEST_F(TestCryptoOpensslDataCryptor, ReturnContextInvalidMode) { + auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx, nullptr); + cryptor->return_context(ctx, CipherMode::CIPHER_MODE_DEC); + ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx, nullptr); + cryptor->return_context(ctx, static_cast<CipherMode>(-1)); +} + +TEST_F(TestCryptoOpensslDataCryptor, EncryptDecrypt) { + auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx, nullptr); + cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV)); + + unsigned char out[sizeof(TEST_DATA)]; + ASSERT_EQ(sizeof(TEST_DATA), + cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA))); + cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC); + ctx = cryptor->get_context(CipherMode::CIPHER_MODE_DEC); + ASSERT_NE(ctx, nullptr); + ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV))); + ASSERT_EQ(sizeof(TEST_DATA), + cryptor->update_context(ctx, out, out, sizeof(TEST_DATA))); + ASSERT_EQ(0, memcmp(out, TEST_DATA, sizeof(TEST_DATA))); + cryptor->return_context(ctx, CipherMode::CIPHER_MODE_DEC); +} + +TEST_F(TestCryptoOpensslDataCryptor, ReuseContext) { + auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx, nullptr); + + ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV))); + unsigned char out[sizeof(TEST_DATA)]; + ASSERT_EQ(sizeof(TEST_DATA), + cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA))); + + ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV_2, sizeof(TEST_IV_2))); + ASSERT_EQ(sizeof(TEST_DATA), + cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA))); + + auto ctx2 = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx2, nullptr); + + ASSERT_EQ(0, cryptor->init_context(ctx2, TEST_IV_2, sizeof(TEST_IV_2))); + unsigned char out2[sizeof(TEST_DATA)]; + ASSERT_EQ(sizeof(TEST_DATA), + cryptor->update_context(ctx2, TEST_DATA, out2, sizeof(TEST_DATA))); + + ASSERT_EQ(0, memcmp(out, out2, sizeof(TEST_DATA))); + + cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC); + cryptor->return_context(ctx2, CipherMode::CIPHER_MODE_ENC); +} + +TEST_F(TestCryptoOpensslDataCryptor, InvalidIVLength) { + auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx, nullptr); + + ASSERT_EQ(-EINVAL, cryptor->init_context(ctx, TEST_IV, 1)); + cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC); +} + +} // namespace openssl +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/crypto/test_mock_BlockCrypto.cc b/src/test/librbd/crypto/test_mock_BlockCrypto.cc new file mode 100644 index 000000000..07dc19437 --- /dev/null +++ b/src/test/librbd/crypto/test_mock_BlockCrypto.cc @@ -0,0 +1,156 @@ +// -*- 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 "librbd/crypto/BlockCrypto.h" +#include "test/librbd/mock/crypto/MockDataCryptor.h" + +#include "librbd/crypto/BlockCrypto.cc" +template class librbd::crypto::BlockCrypto< + librbd::crypto::MockCryptoContext>; + +using ::testing::ExpectationSet; +using ::testing::internal::ExpectationBase; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::_; + +namespace librbd { +namespace crypto { + +MATCHER_P(CompareArrayToString, s, "") { + return (memcmp(arg, s.c_str(), s.length()) == 0); +} + +struct TestMockCryptoBlockCrypto : public TestFixture { + MockDataCryptor* cryptor; + ceph::ref_t<BlockCrypto<MockCryptoContext>> bc; + int cryptor_block_size = 16; + int cryptor_iv_size = 16; + int block_size = 4096; + int data_offset = 0; + ExpectationSet* expectation_set; + + void SetUp() override { + TestFixture::SetUp(); + + cryptor = new MockDataCryptor(); + cryptor->block_size = cryptor_block_size; + bc = new BlockCrypto<MockCryptoContext>( + reinterpret_cast<CephContext*>(m_ioctx.cct()), cryptor, + block_size, data_offset); + expectation_set = new ExpectationSet(); + } + + void TearDown() override { + delete expectation_set; + bc->put(); + TestFixture::TearDown(); + } + + void expect_get_context(CipherMode mode) { + _set_last_expectation( + EXPECT_CALL(*cryptor, get_context(mode)) + .After(*expectation_set).WillOnce(Return( + new MockCryptoContext()))); + } + + void expect_return_context(CipherMode mode) { + _set_last_expectation( + EXPECT_CALL(*cryptor, return_context(_, mode)) + .After(*expectation_set).WillOnce(WithArg<0>( + Invoke([](MockCryptoContext* ctx) { + delete ctx; + })))); + } + + void expect_init_context(const std::string& iv) { + _set_last_expectation( + EXPECT_CALL(*cryptor, init_context(_, CompareArrayToString(iv), + cryptor_iv_size)) + .After(*expectation_set)); + } + + void expect_update_context(const std::string& in_str, int out_ret) { + _set_last_expectation( + EXPECT_CALL(*cryptor, update_context(_, + CompareArrayToString(in_str), + _, in_str.length())) + .After(*expectation_set).WillOnce(Return(out_ret))); + } + + void _set_last_expectation(ExpectationBase& expectation) { + delete expectation_set; + expectation_set = new ExpectationSet(expectation); + } +}; + +TEST_F(TestMockCryptoBlockCrypto, Encrypt) { + uint32_t image_offset = 0x1230 * 512; + + ceph::bufferlist data1; + data1.append(std::string(2048, '1')); + ceph::bufferlist data2; + data2.append(std::string(4096, '2')); + ceph::bufferlist data3; + data3.append(std::string(2048, '3')); + + ceph::bufferlist data; + data.claim_append(data1); + data.claim_append(data2); + data.claim_append(data3); + + expect_get_context(CipherMode::CIPHER_MODE_ENC); + expect_init_context(std::string("\x30\x12\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16)); + expect_update_context(std::string(2048, '1') + std::string(2048, '2'), 4096); + expect_init_context(std::string("\x38\x12\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16)); + expect_update_context(std::string(2048, '2') + std::string(2048, '3'), 4096); + expect_return_context(CipherMode::CIPHER_MODE_ENC); + + ASSERT_EQ(0, bc->encrypt(&data, image_offset)); + + ASSERT_EQ(data.length(), 8192); +} + +TEST_F(TestMockCryptoBlockCrypto, UnalignedImageOffset) { + ceph::bufferlist data; + data.append(std::string(4096, '1')); + ASSERT_EQ(-EINVAL, bc->encrypt(&data, 2)); +} + +TEST_F(TestMockCryptoBlockCrypto, UnalignedDataLength) { + ceph::bufferlist data; + data.append(std::string(512, '1')); + ASSERT_EQ(-EINVAL, bc->encrypt(&data, 0)); +} + +TEST_F(TestMockCryptoBlockCrypto, GetContextError) { + ceph::bufferlist data; + data.append(std::string(4096, '1')); + EXPECT_CALL(*cryptor, get_context(CipherMode::CIPHER_MODE_ENC)).WillOnce( + Return(nullptr)); + ASSERT_EQ(-EIO, bc->encrypt(&data, 0)); +} + +TEST_F(TestMockCryptoBlockCrypto, InitContextError) { + ceph::bufferlist data; + data.append(std::string(4096, '1')); + expect_get_context(CipherMode::CIPHER_MODE_ENC); + EXPECT_CALL(*cryptor, init_context(_, _, _)).WillOnce(Return(-123)); + expect_return_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_EQ(-123, bc->encrypt(&data, 0)); +} + +TEST_F(TestMockCryptoBlockCrypto, UpdateContextError) { + ceph::bufferlist data; + data.append(std::string(4096, '1')); + expect_get_context(CipherMode::CIPHER_MODE_ENC); + EXPECT_CALL(*cryptor, init_context(_, _, _)); + EXPECT_CALL(*cryptor, update_context(_, _, _, _)).WillOnce(Return(-123)); + expect_return_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_EQ(-123, bc->encrypt(&data, 0)); +} + +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/crypto/test_mock_CryptoContextPool.cc b/src/test/librbd/crypto/test_mock_CryptoContextPool.cc new file mode 100644 index 000000000..6eb7877eb --- /dev/null +++ b/src/test/librbd/crypto/test_mock_CryptoContextPool.cc @@ -0,0 +1,54 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "gtest/gtest.h" +#include "librbd/crypto/CryptoContextPool.h" +#include "test/librbd/mock/crypto/MockDataCryptor.h" + +#include "librbd/crypto/CryptoContextPool.cc" +template class librbd::crypto::CryptoContextPool< + librbd::crypto::MockCryptoContext>; + +using ::testing::Return; + +namespace librbd { +namespace crypto { + +struct TestMockCryptoCryptoContextPool : public ::testing::Test { + MockDataCryptor cryptor; + + void expect_get_context(CipherMode mode) { + EXPECT_CALL(cryptor, get_context(mode)).WillOnce(Return( + new MockCryptoContext())); + } + + void expect_return_context(MockCryptoContext* ctx, CipherMode mode) { + delete ctx; + EXPECT_CALL(cryptor, return_context(ctx, mode)); + } +}; + +TEST_F(TestMockCryptoCryptoContextPool, Test) { + CryptoContextPool<MockCryptoContext> pool(&cryptor, 1); + + expect_get_context(CipherMode::CIPHER_MODE_ENC); + auto enc_ctx = pool.get_context(CipherMode::CIPHER_MODE_ENC); + + expect_get_context(CipherMode::CIPHER_MODE_DEC); + auto dec_ctx1 = pool.get_context(CipherMode::CIPHER_MODE_DEC); + expect_get_context(CipherMode::CIPHER_MODE_DEC); + auto dec_ctx2 = pool.get_context(CipherMode::CIPHER_MODE_DEC); + pool.return_context(dec_ctx1, CipherMode::CIPHER_MODE_DEC); + expect_return_context(dec_ctx2, CipherMode::CIPHER_MODE_DEC); + pool.return_context(dec_ctx2, CipherMode::CIPHER_MODE_DEC); + + pool.return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC); + ASSERT_EQ(enc_ctx, pool.get_context(CipherMode::CIPHER_MODE_ENC)); + pool.return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC); + + expect_return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC); + expect_return_context(dec_ctx1, CipherMode::CIPHER_MODE_DEC); +} + +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc b/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc new file mode 100644 index 000000000..04b6b8962 --- /dev/null +++ b/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc @@ -0,0 +1,798 @@ +// -*- 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/librbd/mock/MockObjectMap.h" +#include "test/librbd/mock/crypto/MockCryptoInterface.h" +#include "librbd/crypto/CryptoObjectDispatch.h" +#include "librbd/io/ObjectDispatchSpec.h" +#include "librbd/io/Utils.h" + +#include "librbd/io/Utils.cc" +template bool librbd::io::util::trigger_copyup( + MockImageCtx *image_ctx, uint64_t object_no, IOContext io_context, + Context* on_finish); + +template class librbd::io::ObjectWriteRequest<librbd::MockImageCtx>; +template class librbd::io::AbstractObjectWriteRequest<librbd::MockImageCtx>; +#include "librbd/io/ObjectRequest.cc" + +namespace librbd { + +namespace util { + +inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util + +namespace io { + +template <> +struct CopyupRequest<librbd::MockImageCtx> { + MOCK_METHOD0(send, void()); + MOCK_METHOD2(append_request, void( + AbstractObjectWriteRequest<librbd::MockImageCtx>*, + const Extents&)); + + static CopyupRequest* s_instance; + static CopyupRequest* create(librbd::MockImageCtx *ictx, + uint64_t objectno, Extents &&image_extents, + const ZTracer::Trace &parent_trace) { + return s_instance; + } + + CopyupRequest() { + s_instance = this; + } +}; + +CopyupRequest<librbd::MockImageCtx>* CopyupRequest< + librbd::MockImageCtx>::s_instance = nullptr; + +namespace util { + +template <> uint64_t get_file_offset( + MockImageCtx *image_ctx, uint64_t object_no, uint64_t offset) { + return Striper::get_file_offset(image_ctx->cct, &image_ctx->layout, + object_no, offset); +} + +namespace { + +struct Mock { + static Mock* s_instance; + + Mock() { + s_instance = this; + } + + MOCK_METHOD6(read_parent, + void(MockImageCtx*, uint64_t, io::ReadExtents*, + librados::snap_t, const ZTracer::Trace &, Context*)); +}; + +Mock *Mock::s_instance = nullptr; + +} // anonymous namespace + +template <> void read_parent( + MockImageCtx *image_ctx, uint64_t object_no, + io::ReadExtents* extents, librados::snap_t snap_id, + const ZTracer::Trace &trace, Context* on_finish) { + + Mock::s_instance->read_parent(image_ctx, object_no, extents, snap_id, trace, + on_finish); +} + +} // namespace util +} // namespace io + +} // namespace librbd + +#include "librbd/crypto/CryptoObjectDispatch.cc" + +namespace librbd { +namespace crypto { + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Pair; +using ::testing::Return; +using ::testing::WithArg; + +struct TestMockCryptoCryptoObjectDispatch : public TestMockFixture { + typedef CryptoObjectDispatch<librbd::MockImageCtx> MockCryptoObjectDispatch; + typedef io::AbstractObjectWriteRequest<librbd::MockImageCtx> + MockAbstractObjectWriteRequest; + typedef io::CopyupRequest<librbd::MockImageCtx> MockCopyupRequest; + typedef io::util::Mock MockUtils; + + MockCryptoInterface* crypto; + MockImageCtx* mock_image_ctx; + MockCryptoObjectDispatch* mock_crypto_object_dispatch; + + C_SaferCond finished_cond; + Context *on_finish = &finished_cond; + C_SaferCond dispatched_cond; + Context *on_dispatched = &dispatched_cond; + Context *dispatcher_ctx; + ceph::bufferlist data; + io::DispatchResult dispatch_result; + io::Extents extent_map; + int object_dispatch_flags = 0; + MockUtils mock_utils; + MockCopyupRequest copyup_request; + + void SetUp() override { + TestMockFixture::SetUp(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + crypto = new MockCryptoInterface(); + mock_image_ctx = new MockImageCtx(*ictx); + mock_crypto_object_dispatch = new MockCryptoObjectDispatch( + mock_image_ctx, crypto); + data.append(std::string(4096, '1')); + } + + void TearDown() override { + C_SaferCond cond; + Context *on_finish = &cond; + mock_crypto_object_dispatch->shut_down(on_finish); + ASSERT_EQ(0, cond.wait()); + + delete mock_crypto_object_dispatch; + delete mock_image_ctx; + + TestMockFixture::TearDown(); + } + + void expect_object_read(io::ReadExtents* extents, uint64_t version = 0) { + EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_)) + .WillOnce(Invoke([this, extents, + version](io::ObjectDispatchSpec* spec) { + auto* read = boost::get<io::ObjectDispatchSpec::ReadRequest>( + &spec->request); + ASSERT_TRUE(read != nullptr); + + ASSERT_EQ(extents->size(), read->extents->size()); + for (uint64_t i = 0; i < extents->size(); ++i) { + ASSERT_EQ((*extents)[i].offset, (*read->extents)[i].offset); + ASSERT_EQ((*extents)[i].length, (*read->extents)[i].length); + (*read->extents)[i].bl = (*extents)[i].bl; + (*read->extents)[i].extent_map = (*extents)[i].extent_map; + } + + if (read->version != nullptr) { + *(read->version) = version; + } + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + dispatcher_ctx = &spec->dispatcher_ctx; + })); + } + + void expect_read_parent(MockUtils &mock_utils, uint64_t object_no, + io::ReadExtents* extents, librados::snap_t snap_id, + int r) { + EXPECT_CALL(mock_utils, + read_parent(_, object_no, extents, snap_id, _, _)) + .WillOnce(WithArg<5>(CompleteContext( + r, static_cast<asio::ContextWQ*>(nullptr)))); + } + + void expect_object_write(uint64_t object_off, const std::string& data, + int write_flags, + std::optional<uint64_t> assert_version) { + EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_)) + .WillOnce(Invoke([this, object_off, data, write_flags, + assert_version](io::ObjectDispatchSpec* spec) { + auto* write = boost::get<io::ObjectDispatchSpec::WriteRequest>( + &spec->request); + ASSERT_TRUE(write != nullptr); + + ASSERT_EQ(object_off, write->object_off); + ASSERT_TRUE(data == write->data.to_str()); + ASSERT_EQ(write_flags, write->write_flags); + ASSERT_EQ(assert_version, write->assert_version); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + dispatcher_ctx = &spec->dispatcher_ctx; + })); + } + + void expect_object_write_same() { + EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_)) + .WillOnce(Invoke([this](io::ObjectDispatchSpec* spec) { + auto* write_same = boost::get< + io::ObjectDispatchSpec::WriteSameRequest>( + &spec->request); + ASSERT_TRUE(write_same != nullptr); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + dispatcher_ctx = &spec->dispatcher_ctx; + })); + } + + void expect_get_object_size() { + EXPECT_CALL(*mock_image_ctx, get_object_size()).WillOnce(Return( + mock_image_ctx->layout.object_size)); + } + + void expect_remap_extents(uint64_t offset, uint64_t length) { + EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, remap_extents( + ElementsAre(Pair(offset, length)), + io::IMAGE_EXTENTS_MAP_TYPE_PHYSICAL_TO_LOGICAL)); + } + + void expect_get_parent_overlap(uint64_t overlap) { + EXPECT_CALL(*mock_image_ctx, get_parent_overlap(_, _)) + .WillOnce(WithArg<1>(Invoke([overlap](uint64_t *o) { + *o = overlap; + return 0; + }))); + } + + void expect_prune_parent_extents(uint64_t object_overlap) { + EXPECT_CALL(*mock_image_ctx, prune_parent_extents(_, _)) + .WillOnce(Return(object_overlap)); + } + + void expect_copyup(MockAbstractObjectWriteRequest** write_request, int r) { + EXPECT_CALL(copyup_request, append_request(_, _)) + .WillOnce(WithArg<0>( + Invoke([write_request]( + MockAbstractObjectWriteRequest *req) { + *write_request = req; + }))); + EXPECT_CALL(copyup_request, send()) + .WillOnce(Invoke([write_request, r]() { + (*write_request)->handle_copyup(r); + })); + } + + void expect_encrypt(int count = 1) { + EXPECT_CALL(*crypto, encrypt(_, _)).Times(count); + } + + void expect_decrypt(int count = 1) { + EXPECT_CALL(*crypto, decrypt(_, _)).Times(count); + } +}; + +TEST_F(TestMockCryptoCryptoObjectDispatch, Flush) { + ASSERT_FALSE(mock_crypto_object_dispatch->flush( + io::FLUSH_SOURCE_USER, {}, nullptr, nullptr, &on_finish, nullptr)); + ASSERT_EQ(on_finish, &finished_cond); // not modified + on_finish->complete(0); + ASSERT_EQ(0, finished_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, Discard) { + expect_object_write_same(); + ASSERT_TRUE(mock_crypto_object_dispatch->discard( + 0, 0, 4096, mock_image_ctx->get_data_io_context(), 0, {}, + &object_dispatch_flags, nullptr, &dispatch_result, &on_finish, + on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, AlignedReadFail) { + io::ReadExtents extents = {{0, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->read( + 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {}, + nullptr, &object_dispatch_flags, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(-EIO); + ASSERT_EQ(-EIO, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, AlignedRead) { + io::ReadExtents extents = {{0, 16384}, {32768, 4096}}; + extents[0].bl.append(std::string(1024, '1') + std::string(1024, '2') + + std::string(1024, '3') + std::string(1024, '4')); + extents[0].extent_map = {{1024, 1024}, {3072, 2048}, {16384 - 1024, 1024}}; + extents[1].bl.append(std::string(4096, '0')); + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->read( + 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {}, + nullptr, &object_dispatch_flags, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + expect_decrypt(3); + dispatcher_ctx->complete(0); + ASSERT_EQ(16384 + 4096, dispatched_cond.wait()); + + auto expected_bl_data = ( + std::string(1024, '\0') + std::string(1024, '1') + + std::string(1024, '\0') + std::string(1024, '2') + + std::string(1024, '3') + std::string(3072, '\0') + + std::string(3072, '\0') + std::string(1024, '4')); + ASSERT_TRUE(extents[0].bl.to_str() == expected_bl_data); + ASSERT_THAT(extents[0].extent_map, + ElementsAre(Pair(0, 8192), Pair(16384 - 4096, 4096))); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, ReadFromParent) { + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + expect_read_parent(mock_utils, 0, &extents, CEPH_NOSNAP, 8192); + ASSERT_TRUE(mock_crypto_object_dispatch->read( + 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {}, + nullptr, &object_dispatch_flags, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + // no decrypt + dispatcher_ctx->complete(-ENOENT); + ASSERT_EQ(8192, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, ReadFromParentDisabled) { + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->read( + 0, &extents, mock_image_ctx->get_data_io_context(), 0, + io::READ_FLAG_DISABLE_READ_FROM_PARENT, {}, + nullptr, &object_dispatch_flags, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + // no decrypt + dispatcher_ctx->complete(-ENOENT); + ASSERT_EQ(-ENOENT, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedRead) { + io::ReadExtents extents = {{0, 1}, {8191, 1}, {8193, 1}, + {16384 + 1, 4096 * 5 - 2}}; + io::ReadExtents aligned_extents = {{0, 4096}, {4096, 4096}, {8192, 4096}, + {16384, 4096 * 5}}; + aligned_extents[0].bl.append(std::string("1") + std::string(4096, '0')); + aligned_extents[1].bl.append(std::string(4095, '0') + std::string("2")); + aligned_extents[2].bl.append(std::string("03") + std::string(4094, '0')); + aligned_extents[3].bl.append(std::string("0") + std::string(4095, '4') + + std::string(4096, '5') + + std::string(4095, '6') + std::string("0")); + aligned_extents[3].extent_map = {{16384, 4096}, {16384 + 2 * 4096, 4096}, + {16384 + 4 * 4096, 4096}}; + + expect_object_read(&aligned_extents); + ASSERT_TRUE(mock_crypto_object_dispatch->read( + 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {}, + nullptr, &object_dispatch_flags, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + dispatcher_ctx->complete(4096*8); + ASSERT_EQ(3 + 4096 * 5 - 2, dispatched_cond.wait()); + ASSERT_TRUE(extents[0].bl.to_str() == std::string("1")); + ASSERT_TRUE(extents[1].bl.to_str() == std::string("2")); + ASSERT_TRUE(extents[2].bl.to_str() == std::string("3")); + + auto expected_bl_data = (std::string(4095, '4') + std::string(4096, '5') + + std::string(4095, '6')); + ASSERT_TRUE(extents[3].bl.to_str() == expected_bl_data); + ASSERT_THAT(extents[3].extent_map, + ElementsAre(Pair(16384 + 1, 4095), Pair(16384 + 2 * 4096, 4096), + Pair(16384 + 4 * 4096, 4095))); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, AlignedWrite) { + expect_encrypt(); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 0, std::move(data), mock_image_ctx->get_data_io_context(), 0, 0, + std::nullopt, {}, nullptr, nullptr, &dispatch_result, &on_finish, + on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_CONTINUE); + ASSERT_EQ(on_finish, &finished_cond); // not modified + on_finish->complete(0); + ASSERT_EQ(0, finished_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWrite) { + ceph::bufferlist write_data; + uint64_t version = 1234; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + expect_object_read(&extents, version); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + auto expected_data = + std::string("2") + std::string(8192, '1') + std::string(4095, '3'); + expect_object_write(0, expected_data, 0, std::make_optional(version)); + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteWithNoObject) { + ceph::bufferlist write_data; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + expect_get_object_size(); + expect_get_parent_overlap(0); + auto expected_data = (std::string(1, '\0') + std::string(8192, '1') + + std::string(4095, '\0')); + expect_object_write(0, expected_data, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, + std::nullopt); + dispatcher_ctx->complete(-ENOENT); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteFailCreate) { + ceph::bufferlist write_data; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + expect_get_object_size(); + expect_get_parent_overlap(0); + auto expected_data = (std::string(1, '\0') + std::string(8192, '1') + + std::string(4095, '\0')); + expect_object_write(0, expected_data, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, + std::nullopt); + dispatcher_ctx->complete(-ENOENT); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + uint64_t version = 1234; + expect_object_read(&extents, version); + dispatcher_ctx->complete(-EEXIST); // complete write, request will restart + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + auto expected_data2 = + std::string("2") + std::string(8192, '1') + std::string(4095, '3'); + expect_object_write(0, expected_data2, 0, std::make_optional(version)); + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteCopyup) { + MockObjectMap mock_object_map; + mock_image_ctx->object_map = &mock_object_map; + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx->exclusive_lock = &mock_exclusive_lock; + + ceph::bufferlist write_data; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + expect_get_object_size(); + expect_get_parent_overlap(mock_image_ctx->layout.object_size); + expect_remap_extents(0, mock_image_ctx->layout.object_size); + expect_prune_parent_extents(mock_image_ctx->layout.object_size); + EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillRepeatedly( + Return(true)); + EXPECT_CALL(*mock_image_ctx->object_map, object_may_exist(0)).WillOnce( + Return(false)); + MockAbstractObjectWriteRequest *write_request = nullptr; + expect_copyup(&write_request, 0); + + // unaligned write restarted + uint64_t version = 1234; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + expect_object_read(&extents, version); + dispatcher_ctx->complete(-ENOENT); // complete first read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + auto expected_data = + std::string("2") + std::string(8192, '1') + std::string(4095, '3'); + expect_object_write(0, expected_data, 0, std::make_optional(version)); + dispatcher_ctx->complete(8192); // complete second read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteEmptyCopyup) { + MockObjectMap mock_object_map; + mock_image_ctx->object_map = &mock_object_map; + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx->exclusive_lock = &mock_exclusive_lock; + + ceph::bufferlist write_data; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + expect_get_object_size(); + expect_get_parent_overlap(mock_image_ctx->layout.object_size); + expect_remap_extents(0, mock_image_ctx->layout.object_size); + expect_prune_parent_extents(mock_image_ctx->layout.object_size); + EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillRepeatedly( + Return(true)); + EXPECT_CALL(*mock_image_ctx->object_map, object_may_exist(0)).WillOnce( + Return(false)); + MockAbstractObjectWriteRequest *write_request = nullptr; + expect_copyup(&write_request, 0); + + // unaligned write restarted + expect_object_read(&extents); + dispatcher_ctx->complete(-ENOENT); // complete first read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + auto expected_data = + std::string(1, '\0') + std::string(8192, '1') + + std::string(4095, '\0'); + expect_object_write(0, expected_data, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, + std::nullopt); + dispatcher_ctx->complete(-ENOENT); // complete second read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteFailVersionCheck) { + ceph::bufferlist write_data; + uint64_t version = 1234; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + expect_object_read(&extents, version); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + auto expected_data = + std::string("2") + std::string(8192, '1') + std::string(4095, '3'); + expect_object_write(0, expected_data, 0, std::make_optional(version)); + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + version = 1235; + expect_object_read(&extents, version); + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + dispatcher_ctx->complete(-ERANGE); // complete write, request will restart + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + expect_object_write(0, expected_data, 0, std::make_optional(version)); + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteWithAssertVersion) { + ceph::bufferlist write_data; + uint64_t version = 1234; + uint64_t assert_version = 1233; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + expect_object_read(&extents, version); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::make_optional(assert_version), {}, nullptr, nullptr, + &dispatch_result, &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(-ERANGE, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteWithExclusiveCreate) { + ceph::bufferlist write_data; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, std::nullopt, {}, nullptr, + nullptr, &dispatch_result, &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(-EEXIST, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, CompareAndWrite) { + ceph::bufferlist write_data; + uint64_t version = 1234; + write_data.append(std::string(8192, '1')); + ceph::bufferlist cmp_data; + cmp_data.append(std::string(4096, '2')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}, {0, 8192}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + extents[2].bl.append(std::string(8192, '2')); + expect_object_read(&extents, version); + + ASSERT_TRUE(mock_crypto_object_dispatch->compare_and_write( + 0, 1, std::move(cmp_data), std::move(write_data), + mock_image_ctx->get_data_io_context(), 0, {}, nullptr, nullptr, + nullptr, &dispatch_result, &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + auto expected_data = + std::string("2") + std::string(8192, '1') + std::string(4095, '3'); + expect_object_write(0, expected_data, 0, std::make_optional(version)); + dispatcher_ctx->complete(4096*4); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, CompareAndWriteFail) { + ceph::bufferlist write_data; + uint64_t version = 1234; + write_data.append(std::string(8192, '1')); + ceph::bufferlist cmp_data; + cmp_data.append(std::string(4094, '2') + std::string(2, '4')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}, {0, 8192}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + extents[2].bl.append(std::string(8192, '2')); + expect_object_read(&extents, version); + + uint64_t mismatch_offset; + ASSERT_TRUE(mock_crypto_object_dispatch->compare_and_write( + 0, 1, std::move(cmp_data), std::move(write_data), + mock_image_ctx->get_data_io_context(), 0, {}, &mismatch_offset, + nullptr, nullptr, &dispatch_result, &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + dispatcher_ctx->complete(4096*4); // complete read + ASSERT_EQ(-EILSEQ, dispatched_cond.wait()); + ASSERT_EQ(mismatch_offset, 4094); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, WriteSame) { + io::LightweightBufferExtents buffer_extents; + ceph::bufferlist write_data; + write_data.append(std::string("12")); + expect_object_write(0, std::string("12121") , 0, std::nullopt); + ASSERT_TRUE(mock_crypto_object_dispatch->write_same( + 0, 0, 5, {{0, 5}}, std::move(write_data), + mock_image_ctx->get_data_io_context(), 0, {}, nullptr, nullptr, + &dispatch_result, &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, PrepareCopyup) { + char* data = (char*)"0123456789"; + io::SnapshotSparseBufferlist snapshot_sparse_bufferlist; + auto& snap1 = snapshot_sparse_bufferlist[0]; + auto& snap2 = snapshot_sparse_bufferlist[1]; + + snap1.insert(0, 1, {io::SPARSE_EXTENT_STATE_DATA, 1, + ceph::bufferlist::static_from_mem(data + 1, 1)}); + snap1.insert(8191, 1, {io::SPARSE_EXTENT_STATE_DATA, 1, + ceph::bufferlist::static_from_mem(data + 2, 1)}); + snap1.insert(8193, 3, {io::SPARSE_EXTENT_STATE_DATA, 3, + ceph::bufferlist::static_from_mem(data + 3, 3)}); + + snap2.insert(0, 2, {io::SPARSE_EXTENT_STATE_ZEROED, 2}); + snap2.insert(8191, 3, {io::SPARSE_EXTENT_STATE_DATA, 3, + ceph::bufferlist::static_from_mem(data + 6, 3)}); + snap2.insert(16384, 1, {io::SPARSE_EXTENT_STATE_DATA, 1, + ceph::bufferlist::static_from_mem(data + 9, 1)}); + + expect_get_object_size(); + expect_encrypt(6); + InSequence seq; + expect_remap_extents(0, 4096); + expect_remap_extents(4096, 4096); + expect_remap_extents(8192, 4096); + expect_remap_extents(0, 4096); + expect_remap_extents(4096, 8192); + expect_remap_extents(16384, 4096); + ASSERT_EQ(0, mock_crypto_object_dispatch->prepare_copyup( + 0, &snapshot_sparse_bufferlist)); + + ASSERT_EQ(2, snapshot_sparse_bufferlist.size()); + + auto& snap1_result = snapshot_sparse_bufferlist[0]; + auto& snap2_result = snapshot_sparse_bufferlist[1]; + + auto it = snap1_result.begin(); + ASSERT_NE(it, snap1_result.end()); + ASSERT_EQ(0, it.get_off()); + ASSERT_EQ(4096 * 3, it.get_len()); + + ASSERT_TRUE(it.get_val().bl.to_str() == + std::string("1") + std::string(4095, '\0') + + std::string(4095, '\0') + std::string("2") + + std::string(1, '\0') + std::string("345") + std::string(4092, '\0')); + ASSERT_EQ(++it, snap1_result.end()); + + it = snap2_result.begin(); + ASSERT_NE(it, snap2_result.end()); + ASSERT_EQ(0, it.get_off()); + ASSERT_EQ(4096 * 3, it.get_len()); + ASSERT_TRUE(it.get_val().bl.to_str() == + std::string(4096, '\0') + + std::string(4095, '\0') + std::string("6") + + std::string("7845") + std::string(4092, '\0')); + + ASSERT_NE(++it, snap2_result.end()); + ASSERT_EQ(16384, it.get_off()); + ASSERT_EQ(4096, it.get_len()); + ASSERT_TRUE(it.get_val().bl.to_str() == + std::string("9") + std::string(4095, '\0')); + ASSERT_EQ(++it, snap2_result.end()); +} + +} // namespace io +} // namespace librbd diff --git a/src/test/librbd/crypto/test_mock_FormatRequest.cc b/src/test/librbd/crypto/test_mock_FormatRequest.cc new file mode 100644 index 000000000..2008deb68 --- /dev/null +++ b/src/test/librbd/crypto/test_mock_FormatRequest.cc @@ -0,0 +1,195 @@ +// -*- 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/librbd/mock/crypto/MockCryptoInterface.h" +#include "test/librbd/mock/crypto/MockEncryptionFormat.h" +#include "librbd/crypto/Utils.h" + +namespace librbd { +namespace util { + +inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util +} // namespace librbd + +#include "librbd/crypto/FormatRequest.cc" + +namespace librbd { +namespace crypto { + +namespace util { + +template <> void set_crypto( + MockImageCtx *image_ctx, ceph::ref_t<CryptoInterface> crypto) { + image_ctx->crypto = crypto.get(); +} + +} // namespace util + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; + +template <> +struct ShutDownCryptoRequest<MockImageCtx> { + Context *on_finish = nullptr; + static ShutDownCryptoRequest *s_instance; + static ShutDownCryptoRequest *create( + MockImageCtx* image_ctx, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + ShutDownCryptoRequest() { + s_instance = this; + } +}; + +ShutDownCryptoRequest<MockImageCtx> *ShutDownCryptoRequest< + MockImageCtx>::s_instance = nullptr; + +struct TestMockCryptoFormatRequest : public TestMockFixture { + typedef FormatRequest<librbd::MockImageCtx> MockFormatRequest; + typedef ShutDownCryptoRequest<MockImageCtx> MockShutDownCryptoRequest; + + MockImageCtx* mock_image_ctx; + C_SaferCond finished_cond; + Context *on_finish = &finished_cond; + MockEncryptionFormat* mock_encryption_format; + Context* format_context; + MockCryptoInterface* crypto; + MockCryptoInterface* old_crypto; + MockFormatRequest* mock_format_request; + std::string key = std::string(64, '0'); + + void SetUp() override { + TestMockFixture::SetUp(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + mock_image_ctx = new MockImageCtx(*ictx); + mock_encryption_format = new MockEncryptionFormat(); + crypto = new MockCryptoInterface(); + old_crypto = new MockCryptoInterface(); + mock_image_ctx->crypto = old_crypto; + mock_format_request = MockFormatRequest::create( + mock_image_ctx, + std::unique_ptr<MockEncryptionFormat>(mock_encryption_format), + on_finish); + } + + void TearDown() override { + crypto->put(); + old_crypto->put(); + delete mock_image_ctx; + TestMockFixture::TearDown(); + } + + void expect_test_journal_feature(bool has_journal=false) { + EXPECT_CALL(*mock_image_ctx, test_features( + RBD_FEATURE_JOURNALING)).WillOnce(Return(has_journal)); + } + + void expect_encryption_format() { + EXPECT_CALL(*mock_encryption_format, format( + mock_image_ctx, _)).WillOnce( + WithArg<1>(Invoke([this](Context* ctx) { + format_context = ctx; + }))); + } + + void expect_image_flush(int r) { + EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_)).WillOnce( + Invoke([r](io::ImageDispatchSpec* spec) { + ASSERT_TRUE(boost::get<io::ImageDispatchSpec::Flush>( + &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); + })); + } +}; + +TEST_F(TestMockCryptoFormatRequest, JournalEnabled) { + expect_test_journal_feature(true); + mock_format_request->send(); + ASSERT_EQ(-ENOTSUP, finished_cond.wait()); + ASSERT_EQ(old_crypto, mock_image_ctx->crypto); +} + +TEST_F(TestMockCryptoFormatRequest, FailShutDownCrypto) { + expect_test_journal_feature(false); + MockShutDownCryptoRequest mock_shutdown_crypto_request; + EXPECT_CALL(mock_shutdown_crypto_request, send()); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + mock_shutdown_crypto_request.on_finish->complete(-EIO); + ASSERT_EQ(-EIO, finished_cond.wait()); + ASSERT_EQ(old_crypto, mock_image_ctx->crypto); +} + +TEST_F(TestMockCryptoFormatRequest, FormatFail) { + mock_image_ctx->crypto = nullptr; + expect_test_journal_feature(false); + expect_encryption_format(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + format_context->complete(-EIO); + ASSERT_EQ(-EIO, finished_cond.wait()); + ASSERT_EQ(nullptr, mock_image_ctx->crypto); +} + +TEST_F(TestMockCryptoFormatRequest, Success) { + mock_image_ctx->crypto = nullptr; + expect_test_journal_feature(false); + expect_encryption_format(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_flush(0); + EXPECT_CALL(*mock_encryption_format, get_crypto()).WillOnce(Return(crypto)); + format_context->complete(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_EQ(crypto, mock_image_ctx->crypto); +} + +TEST_F(TestMockCryptoFormatRequest, FailFlush) { + mock_image_ctx->crypto = nullptr; + expect_test_journal_feature(false); + expect_encryption_format(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_flush(-EIO); + format_context->complete(0); + ASSERT_EQ(-EIO, finished_cond.wait()); + ASSERT_EQ(nullptr, mock_image_ctx->crypto); +} + +TEST_F(TestMockCryptoFormatRequest, CryptoAlreadyLoaded) { + expect_test_journal_feature(false); + MockShutDownCryptoRequest mock_shutdown_crypto_request; + EXPECT_CALL(mock_shutdown_crypto_request, send()); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_encryption_format(); + mock_shutdown_crypto_request.on_finish->complete(0); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_flush(0); + EXPECT_CALL(*mock_encryption_format, get_crypto()).WillOnce(Return(crypto)); + format_context->complete(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_EQ(crypto, mock_image_ctx->crypto); +} + +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/crypto/test_mock_LoadRequest.cc b/src/test/librbd/crypto/test_mock_LoadRequest.cc new file mode 100644 index 000000000..f87d4eea2 --- /dev/null +++ b/src/test/librbd/crypto/test_mock_LoadRequest.cc @@ -0,0 +1,125 @@ +// -*- 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 crypto { + +template <> +struct CryptoObjectDispatch<MockImageCtx> : public io::MockObjectDispatch { + + static CryptoObjectDispatch* create( + MockImageCtx* image_ctx,ceph::ref_t<CryptoInterface> crypto) { + return new CryptoObjectDispatch(); + } + + CryptoObjectDispatch() { + } +}; + +} // 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<librbd::MockImageCtx> MockLoadRequest; + + MockImageCtx* mock_image_ctx; + C_SaferCond finished_cond; + Context *on_finish = &finished_cond; + MockEncryptionFormat* mock_encryption_format; + MockCryptoInterface* crypto; + 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 MockImageCtx(*ictx); + mock_encryption_format = new MockEncryptionFormat(); + crypto = new MockCryptoInterface(); + mock_load_request = MockLoadRequest::create( + mock_image_ctx, + std::unique_ptr<MockEncryptionFormat>(mock_encryption_format), + on_finish); + } + + void TearDown() override { + crypto->put(); + delete mock_image_ctx; + TestMockFixture::TearDown(); + } + + void expect_test_journal_feature() { + expect_test_journal_feature(mock_image_ctx, false); + } + + void expect_test_journal_feature(MockImageCtx* ctx, bool has_journal=false) { + EXPECT_CALL(*ctx, test_features( + RBD_FEATURE_JOURNALING)).WillOnce(Return(has_journal)); + } + + void expect_encryption_load() { + EXPECT_CALL(*mock_encryption_format, load( + mock_image_ctx, _)).WillOnce( + WithArgs<1>(Invoke([this](Context* ctx) { + load_context = ctx; + }))); + } + +}; + +TEST_F(TestMockCryptoLoadRequest, CryptoAlreadyLoaded) { + mock_image_ctx->crypto = crypto; + 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, LoadFail) { + expect_test_journal_feature(); + expect_encryption_load(); + mock_load_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + load_context->complete(-EIO); + ASSERT_EQ(-EIO, finished_cond.wait()); +} + +TEST_F(TestMockCryptoLoadRequest, Success) { + mock_image_ctx->parent = nullptr; + expect_test_journal_feature(mock_image_ctx, false); + expect_encryption_load(); + mock_load_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + EXPECT_CALL(*mock_encryption_format, get_crypto()).WillOnce(Return(crypto)); + load_context->complete(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_EQ(crypto, mock_image_ctx->crypto); +} + +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/crypto/test_mock_ShutDownCryptoRequest.cc b/src/test/librbd/crypto/test_mock_ShutDownCryptoRequest.cc new file mode 100644 index 000000000..7585ba292 --- /dev/null +++ b/src/test/librbd/crypto/test_mock_ShutDownCryptoRequest.cc @@ -0,0 +1,144 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#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 "librbd/crypto/ShutDownCryptoRequest.cc" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace crypto { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArgs; + +struct TestMockShutDownCryptoRequest : public TestMockFixture { + typedef ShutDownCryptoRequest<MockTestImageCtx> MockShutDownCryptoRequest; + + MockTestImageCtx* mock_image_ctx; + C_SaferCond finished_cond; + Context *on_finish = &finished_cond; + MockShutDownCryptoRequest* mock_shutdown_crypto_request; + MockCryptoInterface* crypto; + Context* shutdown_object_dispatch_context; + Context* shutdown_image_dispatch_context; + + void SetUp() override { + TestMockFixture::SetUp(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + mock_image_ctx = new MockTestImageCtx(*ictx); + crypto = new MockCryptoInterface(); + mock_image_ctx->crypto = crypto; + mock_shutdown_crypto_request = MockShutDownCryptoRequest::create( + mock_image_ctx, on_finish); + } + + void TearDown() override { + crypto->put(); + delete mock_image_ctx; + TestMockFixture::TearDown(); + } + + void expect_crypto_object_layer_exists_check(bool exists) { + EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, exists( + io::OBJECT_DISPATCH_LAYER_CRYPTO)).WillOnce(Return(exists)); + } + + void expect_crypto_image_layer_exists_check(bool exists) { + EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, exists( + io::IMAGE_DISPATCH_LAYER_CRYPTO)).WillOnce(Return(exists)); + } + + void expect_shutdown_crypto_object_dispatch() { + EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, shut_down_dispatch( + io::OBJECT_DISPATCH_LAYER_CRYPTO, _)).WillOnce( + WithArgs<1>(Invoke([this](Context* ctx) { + shutdown_object_dispatch_context = ctx; + }))); + } + + void expect_shutdown_crypto_image_dispatch() { + EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, shut_down_dispatch( + io::IMAGE_DISPATCH_LAYER_CRYPTO, _)).WillOnce( + WithArgs<1>(Invoke([this](Context* ctx) { + shutdown_image_dispatch_context = ctx; + }))); + } +}; + +TEST_F(TestMockShutDownCryptoRequest, NoCryptoObjectDispatch) { + expect_crypto_object_layer_exists_check(false); + mock_shutdown_crypto_request->send(); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_EQ(nullptr, mock_image_ctx->crypto); +} + +TEST_F(TestMockShutDownCryptoRequest, FailShutdownObjectDispatch) { + expect_crypto_object_layer_exists_check(true); + expect_shutdown_crypto_object_dispatch(); + mock_shutdown_crypto_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + shutdown_object_dispatch_context->complete(-EIO); + ASSERT_EQ(-EIO, finished_cond.wait()); + ASSERT_NE(nullptr, mock_image_ctx->crypto); +} + +TEST_F(TestMockShutDownCryptoRequest, NoCryptoImageDispatch) { + expect_crypto_object_layer_exists_check(true); + expect_shutdown_crypto_object_dispatch(); + mock_shutdown_crypto_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_crypto_image_layer_exists_check(false); + shutdown_object_dispatch_context->complete(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_EQ(nullptr, mock_image_ctx->crypto); +} + +TEST_F(TestMockShutDownCryptoRequest, FailShutdownImageDispatch) { + expect_crypto_object_layer_exists_check(true); + expect_shutdown_crypto_object_dispatch(); + mock_shutdown_crypto_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_crypto_image_layer_exists_check(true); + expect_shutdown_crypto_image_dispatch(); + shutdown_object_dispatch_context->complete(0); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + shutdown_image_dispatch_context->complete(-EIO); + ASSERT_EQ(-EIO, finished_cond.wait()); + ASSERT_NE(nullptr, mock_image_ctx->crypto); +} + +TEST_F(TestMockShutDownCryptoRequest, Success) { + expect_crypto_object_layer_exists_check(true); + expect_shutdown_crypto_object_dispatch(); + mock_shutdown_crypto_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_crypto_image_layer_exists_check(true); + expect_shutdown_crypto_image_dispatch(); + shutdown_object_dispatch_context->complete(0); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + shutdown_image_dispatch_context->complete(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_EQ(nullptr, mock_image_ctx->crypto); +} + +} // namespace crypto +} // namespace librbd |