summaryrefslogtreecommitdiffstats
path: root/src/test/librbd/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/librbd/crypto')
-rw-r--r--src/test/librbd/crypto/luks/test_mock_FlattenRequest.cc266
-rw-r--r--src/test/librbd/crypto/luks/test_mock_FormatRequest.cc230
-rw-r--r--src/test/librbd/crypto/luks/test_mock_LoadRequest.cc333
-rw-r--r--src/test/librbd/crypto/openssl/test_DataCryptor.cc118
-rw-r--r--src/test/librbd/crypto/test_mock_BlockCrypto.cc156
-rw-r--r--src/test/librbd/crypto/test_mock_CryptoContextPool.cc54
-rw-r--r--src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc800
-rw-r--r--src/test/librbd/crypto/test_mock_FormatRequest.cc231
-rw-r--r--src/test/librbd/crypto/test_mock_LoadRequest.cc382
-rw-r--r--src/test/librbd/crypto/test_mock_ShutDownCryptoRequest.cc174
10 files changed, 2744 insertions, 0 deletions
diff --git a/src/test/librbd/crypto/luks/test_mock_FlattenRequest.cc b/src/test/librbd/crypto/luks/test_mock_FlattenRequest.cc
new file mode 100644
index 000000000..bc615bcf7
--- /dev/null
+++ b/src/test/librbd/crypto/luks/test_mock_FlattenRequest.cc
@@ -0,0 +1,266 @@
+// -*- 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"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+#include "librbd/crypto/luks/FlattenRequest.cc"
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+
+struct TestMockCryptoLuksFlattenRequest : public TestMockFixture {
+ typedef FlattenRequest<MockTestImageCtx> MockFlattenRequest;
+
+ const size_t OBJECT_SIZE = 4 * 1024 * 1024;
+ const uint64_t DATA_OFFSET = MockCryptoInterface::DATA_OFFSET;
+ const char* passphrase_cstr = "password";
+ std::string passphrase = passphrase_cstr;
+
+ MockTestImageCtx* mock_image_ctx;
+ MockFlattenRequest* mock_flatten_request;
+ MockEncryptionFormat* mock_encryption_format;
+ MockCryptoInterface mock_crypto;
+ C_SaferCond finished_cond;
+ Context *on_finish = &finished_cond;
+ Context* image_read_request;
+ io::AioCompletion* aio_comp;
+ ceph::bufferlist header_bl;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ mock_image_ctx = new MockTestImageCtx(*ictx);
+ mock_encryption_format = new MockEncryptionFormat();
+ mock_image_ctx->encryption_format.reset(mock_encryption_format);
+ mock_flatten_request = MockFlattenRequest::create(
+ mock_image_ctx, on_finish);
+ }
+
+ void TearDown() override {
+ delete mock_image_ctx;
+ TestMockFixture::TearDown();
+ }
+
+ void generate_header(const char* type, const char* alg, size_t key_size,
+ const char* cipher_mode, uint32_t sector_size,
+ bool magic_switched) {
+ 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_LT(0, header.read(&header_bl));
+ if (magic_switched) {
+ ASSERT_EQ(0, Magic::replace_magic(mock_image_ctx->cct, header_bl));
+ }
+ }
+
+ void expect_get_crypto() {
+ EXPECT_CALL(*mock_encryption_format, get_crypto()).WillOnce(
+ Return(&mock_crypto));
+ }
+
+ 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;
+ }));
+ }
+
+ 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;
+
+ // patch header_bl with write
+ bufferlist bl;
+ bl.substr_of(header_bl, write->bl.length(),
+ header_bl.length() - write->bl.length());
+ header_bl = write->bl;
+ header_bl.claim_append(bl);
+ }));
+ }
+
+ 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);
+ }));
+ }
+
+ 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) {
+ 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));
+ }
+};
+
+TEST_F(TestMockCryptoLuksFlattenRequest, LUKS1) {
+ generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512, true);
+ expect_get_crypto();
+ expect_image_read(0, DATA_OFFSET);
+ mock_flatten_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_write();
+ image_read_request->complete(DATA_OFFSET);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_flush(0);
+ complete_aio(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS1));
+ ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoLuksFlattenRequest, LUKS2) {
+ generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, true);
+ expect_get_crypto();
+ expect_image_read(0, DATA_OFFSET);
+ mock_flatten_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_write();
+ image_read_request->complete(DATA_OFFSET);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_flush(0);
+ complete_aio(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS2));
+ ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoLuksFlattenRequest, FailedRead) {
+ generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, true);
+ expect_get_crypto();
+ expect_image_read(0, DATA_OFFSET);
+ mock_flatten_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ image_read_request->complete(-EIO);
+ ASSERT_EQ(-EIO, finished_cond.wait());
+ ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoLuksFlattenRequest, AlreadyFlattened) {
+ generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, false);
+ expect_get_crypto();
+ expect_image_read(0, DATA_OFFSET);
+ mock_flatten_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_write();
+ image_read_request->complete(DATA_OFFSET);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_flush(0);
+ complete_aio(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS2));
+ ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoLuksFlattenRequest, FailedWrite) {
+ generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, true);
+ expect_get_crypto();
+ expect_image_read(0, DATA_OFFSET);
+ mock_flatten_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_write();
+ image_read_request->complete(DATA_OFFSET);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ complete_aio(-EIO);
+ ASSERT_EQ(-EIO, finished_cond.wait());
+ ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoLuksFlattenRequest, FailedFlush) {
+ generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, true);
+ expect_get_crypto();
+ expect_image_read(0, DATA_OFFSET);
+ mock_flatten_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_write();
+ image_read_request->complete(DATA_OFFSET);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_flush(-EIO);
+ complete_aio(0);
+ ASSERT_EQ(-EIO, finished_cond.wait());
+ ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
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..86026b456
--- /dev/null
+++ b/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc
@@ -0,0 +1,230 @@
+// -*- 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;
+ std::unique_ptr<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);
+ }
+
+ void TearDown() override {
+ delete mock_image_ctx;
+ TestMockFixture::TearDown();
+ }
+
+ void expect_get_stripe_period() {
+ EXPECT_CALL(*mock_image_ctx, get_stripe_period()).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, bool magic_switched) {
+ Header header(mock_image_ctx->cct);
+
+ ASSERT_EQ(0, header.init());
+
+ if (magic_switched) {
+ Header non_switched_header(mock_image_ctx->cct);
+ ASSERT_EQ(0, non_switched_header.init());
+ ASSERT_EQ(0, non_switched_header.write(header_bl));
+ ASSERT_EQ(-EINVAL, non_switched_header.load(expected_format));
+ ASSERT_EQ(0, Magic::replace_magic(mock_image_ctx->cct, header_bl));
+ }
+ 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_stripe_period();
+ 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, false));
+}
+
+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_stripe_period();
+ 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, false));
+}
+
+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_stripe_period();
+ 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, false));
+}
+
+TEST_F(TestMockCryptoLuksFormatRequest, LUKS1OnCloned) {
+ mock_image_ctx->parent = mock_image_ctx;
+ auto mock_format_request = MockFormatRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1,
+ RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto,
+ on_finish, true);
+ expect_get_stripe_period();
+ 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, 64, 512, true));
+}
+
+TEST_F(TestMockCryptoLuksFormatRequest, LUKS2OnCloned) {
+ mock_image_ctx->parent = mock_image_ctx;
+ 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_stripe_period();
+ 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, true));
+}
+
+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_stripe_period();
+ 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_stripe_period();
+ 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..5fb566a07
--- /dev/null
+++ b/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc
@@ -0,0 +1,333 @@
+// -*- 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;
+ std::unique_ptr<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;
+ std::string detected_format_name;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ mock_image_ctx = new MockImageCtx(*ictx);
+ mock_load_request = MockLoadRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, std::move(passphrase),
+ &crypto, &detected_format_name, on_finish);
+ detected_format_name = "";
+ }
+
+ void TearDown() override {
+ delete mock_image_ctx;
+ 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,
+ bool magic_switched) {
+ 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_LT(0, header.read(&header_bl));
+ if (magic_switched) {
+ ASSERT_EQ(0, Magic::replace_magic(mock_image_ctx->cct, 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;
+ }));
+ }
+
+ void expect_get_image_size(uint64_t size) {
+ EXPECT_CALL(*mock_image_ctx, get_image_size(_)).WillOnce(
+ Return(size));
+ }
+
+ void expect_get_stripe_period(uint64_t period) {
+ EXPECT_CALL(*mock_image_ctx, get_stripe_period()).WillOnce(
+ Return(period));
+ }
+};
+
+TEST_F(TestMockCryptoLuksLoadRequest, AES128) {
+ generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, false);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ expect_get_image_size(OBJECT_SIZE << 5);
+ expect_get_stripe_period(OBJECT_SIZE);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, AES256) {
+ generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ expect_get_image_size(OBJECT_SIZE << 5);
+ expect_get_stripe_period(OBJECT_SIZE);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, LUKS1) {
+ delete mock_load_request;
+ mock_load_request = MockLoadRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1, {passphrase_cstr}, &crypto,
+ &detected_format_name, on_finish);
+ generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512, false);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ expect_get_image_size(OBJECT_SIZE << 5);
+ expect_get_stripe_period(OBJECT_SIZE);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS1", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, LUKS1ViaLUKS) {
+ delete mock_load_request;
+ mock_load_request = MockLoadRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS, {passphrase_cstr}, &crypto,
+ &detected_format_name, on_finish);
+ generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512, false);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ expect_get_image_size(OBJECT_SIZE << 5);
+ expect_get_stripe_period(OBJECT_SIZE);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS1", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, UnknownFormat) {
+ header_bl.append_zero(MAXIMUM_HEADER_SIZE);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ mock_load_request->send();
+
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+
+ ASSERT_EQ(-EINVAL, finished_cond.wait());
+ ASSERT_EQ(crypto.get(), nullptr);
+ ASSERT_EQ("<unknown>", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, WrongFormat) {
+ generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512, false);
+ 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);
+ image_read_request->complete(MAXIMUM_HEADER_SIZE - DEFAULT_INITIAL_READ_SIZE);
+
+ ASSERT_EQ(-EINVAL, finished_cond.wait());
+ ASSERT_EQ(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedAlgorithm) {
+ generate_header(CRYPT_LUKS2, "twofish", 32, "xts-plain64", 4096, false);
+ 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.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedCipherMode) {
+ generate_header(CRYPT_LUKS2, "aes", 32, "cbc-essiv:sha256", 4096, false);
+ 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.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, BadSize) {
+ generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ expect_get_image_size(OBJECT_SIZE - 1);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(-EINVAL, finished_cond.wait());
+ ASSERT_EQ(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, BadStripePattern) {
+ generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ expect_get_image_size(OBJECT_SIZE << 5);
+ expect_get_stripe_period(OBJECT_SIZE * 3);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(-EINVAL, finished_cond.wait());
+ ASSERT_EQ(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, HeaderBiggerThanInitialRead) {
+ generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false);
+ mock_load_request->set_initial_read_size(4096);
+ expect_image_read(0, 4096);
+ mock_load_request->send();
+
+ expect_get_image_size(OBJECT_SIZE << 5);
+ expect_get_stripe_period(OBJECT_SIZE);
+ 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.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, LUKS1FormattedClone) {
+ mock_image_ctx->parent = mock_image_ctx;
+ delete mock_load_request;
+ mock_load_request = MockLoadRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1, {passphrase_cstr}, &crypto,
+ &detected_format_name, on_finish);
+ generate_header(CRYPT_LUKS1, "aes", 64, "xts-plain64", 512, true);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ expect_get_image_size(OBJECT_SIZE << 5);
+ expect_get_stripe_period(OBJECT_SIZE);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS1", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, LUKS2FormattedClone) {
+ mock_image_ctx->parent = mock_image_ctx;
+ generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, true);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ expect_get_image_size(OBJECT_SIZE << 5);
+ expect_get_stripe_period(OBJECT_SIZE);
+ mock_load_request->send();
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, KeyslotsBiggerThanInitialRead) {
+ generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false);
+ mock_load_request->set_initial_read_size(16384);
+ expect_image_read(0, 16384);
+ mock_load_request->send();
+
+ expect_get_image_size(OBJECT_SIZE << 5);
+ expect_get_stripe_period(OBJECT_SIZE);
+ 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.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, WrongPassphrase) {
+ delete mock_load_request;
+ mock_load_request = MockLoadRequest::create(
+ mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, "wrong", &crypto,
+ &detected_format_name, on_finish);
+
+ generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false);
+ expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+ expect_get_image_size(OBJECT_SIZE << 5);
+ expect_get_stripe_period(OBJECT_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.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
+}
+
+} // 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..56b0772c0
--- /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;
+ 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;
+ delete bc;
+ 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..bfd292616
--- /dev/null
+++ b/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc
@@ -0,0 +1,800 @@
+// -*- 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, ImageArea area,
+ const ZTracer::Trace& parent_trace) {
+ return s_instance;
+ }
+
+ CopyupRequest() {
+ s_instance = this;
+ }
+};
+
+CopyupRequest<librbd::MockImageCtx>* CopyupRequest<
+ librbd::MockImageCtx>::s_instance = nullptr;
+
+namespace util {
+
+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 {
+
+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);
+}
+
+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));
+ 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_to_logical(uint64_t offset, uint64_t length) {
+ EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, remap_to_logical(
+ ElementsAre(Pair(offset, length))));
+ }
+
+ 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(
+ 11, 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(
+ 11, &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(
+ 11, &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, 11, &extents, CEPH_NOSNAP, 8192);
+ ASSERT_TRUE(mock_crypto_object_dispatch->read(
+ 11, &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(
+ 11, &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(
+ 11, &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(
+ 11, 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(
+ 11, 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(
+ 11, 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(
+ 11, 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(
+ 11, 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(100 << 20);
+ expect_remap_to_logical(11 * mock_image_ctx->layout.object_size,
+ 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(11)).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(
+ 11, 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(100 << 20);
+ expect_remap_to_logical(11 * mock_image_ctx->layout.object_size,
+ 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(11)).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(
+ 11, 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(
+ 11, 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(
+ 11, 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(
+ 11, 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(
+ 11, 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(
+ 11, 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;
+ uint64_t base = 11 * mock_image_ctx->layout.object_size;
+ expect_remap_to_logical(base, 4096);
+ expect_remap_to_logical(base + 4096, 4096);
+ expect_remap_to_logical(base + 8192, 4096);
+ expect_remap_to_logical(base, 4096);
+ expect_remap_to_logical(base + 4096, 8192);
+ expect_remap_to_logical(base + 16384, 4096);
+ ASSERT_EQ(0, mock_crypto_object_dispatch->prepare_copyup(
+ 11, &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 crypto
+} // 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..81b82429d
--- /dev/null
+++ b/src/test/librbd/crypto/test_mock_FormatRequest.cc
@@ -0,0 +1,231 @@
+// -*- 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 {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace crypto {
+namespace {
+
+} // anonymous namespace
+
+namespace util {
+
+template <>
+void set_crypto(MockTestImageCtx *image_ctx,
+ std::unique_ptr<MockEncryptionFormat> encryption_format) {
+ image_ctx->encryption_format = std::move(encryption_format);
+}
+
+} // namespace util
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::WithArg;
+
+template <>
+struct ShutDownCryptoRequest<MockTestImageCtx> {
+ Context *on_finish = nullptr;
+ static ShutDownCryptoRequest *s_instance;
+ static ShutDownCryptoRequest *create(MockTestImageCtx *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<MockTestImageCtx> *ShutDownCryptoRequest<
+ MockTestImageCtx>::s_instance = nullptr;
+
+struct TestMockCryptoFormatRequest : public TestMockFixture {
+ typedef FormatRequest<librbd::MockTestImageCtx> MockFormatRequest;
+ typedef ShutDownCryptoRequest<MockTestImageCtx> MockShutDownCryptoRequest;
+
+ MockTestImageCtx* mock_image_ctx;
+ MockTestImageCtx* mock_parent_image_ctx;
+ C_SaferCond finished_cond;
+ Context *on_finish = &finished_cond;
+ MockShutDownCryptoRequest mock_shutdown_crypto_request;
+ MockEncryptionFormat* old_encryption_format;
+ MockEncryptionFormat* new_encryption_format;
+ Context* format_context;
+ 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 MockTestImageCtx(*ictx);
+ mock_parent_image_ctx = new MockTestImageCtx(*ictx);
+ old_encryption_format = new MockEncryptionFormat();
+ new_encryption_format = new MockEncryptionFormat();
+ mock_image_ctx->encryption_format.reset(old_encryption_format);
+ mock_format_request = MockFormatRequest::create(
+ mock_image_ctx,
+ std::unique_ptr<MockEncryptionFormat>(new_encryption_format),
+ on_finish);
+ }
+
+ void TearDown() override {
+ delete mock_parent_image_ctx;
+ 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_shutdown_crypto(int r = 0) {
+ EXPECT_CALL(mock_shutdown_crypto_request, send()).WillOnce(
+ Invoke([this, r]() {
+ if (r == 0) {
+ mock_image_ctx->encryption_format.reset();
+ }
+ mock_shutdown_crypto_request.on_finish->complete(r);
+ }));
+ }
+
+ void expect_encryption_format() {
+ EXPECT_CALL(*new_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_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoFormatRequest, FailShutDownCrypto) {
+ expect_test_journal_feature(false);
+ expect_shutdown_crypto(-EIO);
+ mock_format_request->send();
+ ASSERT_EQ(-EIO, finished_cond.wait());
+ ASSERT_EQ(old_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoFormatRequest, FormatFail) {
+ mock_image_ctx->encryption_format = 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->encryption_format);
+}
+
+TEST_F(TestMockCryptoFormatRequest, Success) {
+ mock_image_ctx->encryption_format = 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);
+ format_context->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(new_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoFormatRequest, FailFlush) {
+ expect_test_journal_feature(false);
+ expect_shutdown_crypto();
+ 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->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoFormatRequest, CryptoAlreadyLoaded) {
+ expect_test_journal_feature(false);
+ expect_shutdown_crypto();
+ expect_encryption_format();
+ mock_format_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_flush(0);
+ format_context->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(new_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoFormatRequest, ThinFormat) {
+ mock_image_ctx->encryption_format = nullptr;
+ mock_image_ctx->parent = mock_parent_image_ctx;
+ expect_test_journal_feature(false);
+ expect_encryption_format();
+ mock_format_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_image_flush(0);
+ format_context->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(nullptr, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoFormatRequest, ThinFormatEncryptionLoaded) {
+ mock_image_ctx->parent = mock_parent_image_ctx;
+ expect_test_journal_feature(false);
+ mock_format_request->send();
+ ASSERT_EQ(-EINVAL, finished_cond.wait());
+}
+
+} // 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..849710d82
--- /dev/null
+++ b/src/test/librbd/crypto/test_mock_LoadRequest.cc
@@ -0,0 +1,382 @@
+// -*- 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<MockEncryptionFormat> 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<librbd::MockTestImageCtx> 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<std::unique_ptr<MockEncryptionFormat>> 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<MockEncryptionFormat>(
+ 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<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);
+ }));
+ }
+
+ 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<std::unique_ptr<MockEncryptionFormat>> 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<std::unique_ptr<MockEncryptionFormat>> 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<std::unique_ptr<MockEncryptionFormat>> 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<std::unique_ptr<MockEncryptionFormat>> 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<MockImageCtx>::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<std::unique_ptr<MockEncryptionFormat>> 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<std::unique_ptr<MockEncryptionFormat>> 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<std::unique_ptr<MockEncryptionFormat>> 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
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..7df99b78a
--- /dev/null
+++ b/src/test/librbd/crypto/test_mock_ShutDownCryptoRequest.cc
@@ -0,0 +1,174 @@
+// -*- 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/MockEncryptionFormat.h"
+
+#include "librbd/crypto/ShutDownCryptoRequest.cc"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+
+ MockTestImageCtx *parent = nullptr;
+};
+
+} // 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;
+ MockEncryptionFormat* mock_encryption_format;
+ 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);
+ mock_encryption_format = new MockEncryptionFormat();
+ mock_image_ctx->encryption_format.reset(mock_encryption_format);
+ mock_shutdown_crypto_request = MockShutDownCryptoRequest::create(
+ mock_image_ctx, on_finish);
+ }
+
+ void TearDown() override {
+ delete mock_image_ctx;
+ TestMockFixture::TearDown();
+ }
+
+ void expect_crypto_object_layer_exists_check(
+ MockTestImageCtx* image_ctx, bool exists) {
+ EXPECT_CALL(*image_ctx->io_object_dispatcher, exists(
+ io::OBJECT_DISPATCH_LAYER_CRYPTO)).WillOnce(Return(exists));
+ }
+
+ void expect_crypto_image_layer_exists_check(
+ MockTestImageCtx* image_ctx, bool exists) {
+ EXPECT_CALL(*image_ctx->io_image_dispatcher, exists(
+ io::IMAGE_DISPATCH_LAYER_CRYPTO)).WillOnce(Return(exists));
+ }
+
+ void expect_shutdown_crypto_object_dispatch(MockTestImageCtx* image_ctx) {
+ EXPECT_CALL(*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(MockTestImageCtx* image_ctx) {
+ EXPECT_CALL(*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(mock_image_ctx, false);
+ mock_shutdown_crypto_request->send();
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(nullptr, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockShutDownCryptoRequest, FailShutdownObjectDispatch) {
+ expect_crypto_object_layer_exists_check(mock_image_ctx, true);
+ expect_shutdown_crypto_object_dispatch(mock_image_ctx);
+ 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_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockShutDownCryptoRequest, NoCryptoImageDispatch) {
+ expect_crypto_object_layer_exists_check(mock_image_ctx, true);
+ expect_shutdown_crypto_object_dispatch(mock_image_ctx);
+ mock_shutdown_crypto_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_crypto_image_layer_exists_check(mock_image_ctx, false);
+ shutdown_object_dispatch_context->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(nullptr, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockShutDownCryptoRequest, FailShutdownImageDispatch) {
+ expect_crypto_object_layer_exists_check(mock_image_ctx, true);
+ expect_shutdown_crypto_object_dispatch(mock_image_ctx);
+ mock_shutdown_crypto_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_crypto_image_layer_exists_check(mock_image_ctx, true);
+ expect_shutdown_crypto_image_dispatch(mock_image_ctx);
+ 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_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockShutDownCryptoRequest, Success) {
+ expect_crypto_object_layer_exists_check(mock_image_ctx, true);
+ expect_shutdown_crypto_object_dispatch(mock_image_ctx);
+ mock_shutdown_crypto_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_crypto_image_layer_exists_check(mock_image_ctx, true);
+ expect_shutdown_crypto_image_dispatch(mock_image_ctx);
+ 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->encryption_format.get());
+}
+
+TEST_F(TestMockShutDownCryptoRequest, ShutdownParent) {
+ auto parent_image_ctx = new MockTestImageCtx(*mock_image_ctx->image_ctx);
+ mock_image_ctx->parent = parent_image_ctx;
+ expect_crypto_object_layer_exists_check(mock_image_ctx, true);
+ expect_shutdown_crypto_object_dispatch(mock_image_ctx);
+ mock_shutdown_crypto_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_crypto_image_layer_exists_check(mock_image_ctx, true);
+ expect_shutdown_crypto_image_dispatch(mock_image_ctx);
+ shutdown_object_dispatch_context->complete(0);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_crypto_object_layer_exists_check(parent_image_ctx, true);
+ expect_shutdown_crypto_object_dispatch(parent_image_ctx);
+ shutdown_image_dispatch_context->complete(0);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_crypto_image_layer_exists_check(parent_image_ctx, true);
+ expect_shutdown_crypto_image_dispatch(parent_image_ctx);
+ shutdown_object_dispatch_context->complete(0);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ mock_image_ctx->parent = nullptr;
+ shutdown_image_dispatch_context->complete(0);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(nullptr, mock_image_ctx->encryption_format.get());
+ ASSERT_EQ(nullptr, parent_image_ctx->encryption_format.get());
+ delete parent_image_ctx;
+}
+
+} // namespace crypto
+} // namespace librbd