read_req = new C_UnalignedObjectReadRequest<I>(
image_ctx, crypto, object_no, &extents, io_context,
- 0, 0, parent_trace, &version, 0, ctx);
+ 0, io::READ_FLAG_DISABLE_READ_FROM_PARENT, parent_trace,
+ &version, 0, ctx);
}
void send() {
}
}
+ void handle_copyup(int r) {
+ ldout(image_ctx->cct, 20) << "r=" << r << dendl;
+ if (r < 0) {
+ complete(r);
+ } else {
+ restart_request();
+ }
+ }
+
void handle_read(int r) {
ldout(image_ctx->cct, 20) << "r=" << r << dendl;
if (r == -ENOENT) {
+ auto ctx = create_context_callback<
+ C_UnalignedObjectWriteRequest<I>,
+ &C_UnalignedObjectWriteRequest<I>::handle_copyup>(this);
+ if (io::util::trigger_copyup(image_ctx, object_no, io_context, ctx)) {
+ return;
+ }
object_exists = false;
} else if (r < 0) {
complete(r);
write_req->send();
}
+ void restart_request() {
+ auto req = new C_UnalignedObjectWriteRequest<I>(
+ image_ctx, crypto, object_no, object_off,
+ std::move(data), std::move(cmp_data),
+ mismatch_offset, io_context, op_flags, write_flags,
+ assert_version, parent_trace,
+ object_dispatch_flags, journal_tid, this);
+ req->send();
+ }
+
void handle_write(int r) {
bool exclusive = write_flags & io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE;
bool restart = false;
}
if (restart) {
- auto req = new C_UnalignedObjectWriteRequest<I>(
- image_ctx, crypto, object_no, object_off,
- std::move(data), std::move(cmp_data),
- mismatch_offset, io_context, op_flags, write_flags,
- assert_version, parent_trace,
- object_dispatch_flags, journal_tid, this);
- req->send();
+ restart_request();
} else {
complete(r);
}
#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);
+
+#include "librbd/io/ObjectRequest.cc"
+template class librbd::io::ObjectWriteRequest<librbd::MockImageCtx>;
+template class librbd::io::AbstractObjectWriteRequest<librbd::MockImageCtx>;
+
namespace librbd {
namespace util {
} // namespace util
namespace io {
+
+template <>
+struct CopyupRequest<librbd::MockImageCtx> {
+ MOCK_METHOD0(send, void());
+ MOCK_METHOD2(append_request, void(
+ AbstractObjectWriteRequest<librbd::MockImageCtx>*,
+ const Extents&));
+
+ static CopyupRequest* s_instance;
+ static CopyupRequest* create(librbd::MockImageCtx *ictx,
+ uint64_t objectno, Extents &&image_extents,
+ const ZTracer::Trace &parent_trace) {
+ return s_instance;
+ }
+
+ CopyupRequest() {
+ s_instance = this;
+ }
+};
+
+CopyupRequest<librbd::MockImageCtx>* CopyupRequest<
+ librbd::MockImageCtx>::s_instance = nullptr;
+
namespace util {
namespace {
using ::testing::ElementsAre;
using ::testing::Invoke;
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;
io::Extents extent_map;
int object_dispatch_flags = 0;
MockUtils mock_utils;
+ MockCopyupRequest copyup_request;
void SetUp() override {
TestMockFixture::SetUp();
ASSERT_EQ(0, open_image(m_image_name, &ictx));
crypto = new MockCryptoInterface();
mock_image_ctx = new MockImageCtx(*ictx);
- mock_crypto_object_dispatch = new MockCryptoObjectDispatch(mock_image_ctx, crypto);
+ mock_crypto_object_dispatch = new MockCryptoObjectDispatch(
+ mock_image_ctx, crypto);
data.append(std::string(4096, '1'));
}
int r) {
EXPECT_CALL(mock_utils,
read_parent(_, object_no, extents, snap_id, _, _))
- .WillOnce(WithArg<5>(CompleteContext(r, static_cast<asio::ContextWQ*>(nullptr))));
+ .WillOnce(WithArg<5>(CompleteContext(
+ r, static_cast<asio::ContextWQ*>(nullptr))));
}
void expect_object_write(uint64_t object_off, const std::string& data,
}));
}
+ void expect_get_object_size() {
+ EXPECT_CALL(*mock_image_ctx, get_object_size()).WillOnce(Return(
+ mock_image_ctx->layout.object_size));
+ }
+
+ 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);
}
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,
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,
ASSERT_EQ(0, dispatched_cond.wait());
}
+TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteCopyup) {
+ MockObjectMap mock_object_map;
+ mock_image_ctx->object_map = &mock_object_map;
+ MockExclusiveLock mock_exclusive_lock;
+ mock_image_ctx->exclusive_lock = &mock_exclusive_lock;
+
+ ceph::bufferlist write_data;
+ write_data.append(std::string(8192, '1'));
+ io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+ expect_object_read(&extents);
+ ASSERT_TRUE(mock_crypto_object_dispatch->write(
+ 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(),
+ 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result,
+ &on_finish, on_dispatched));
+ ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
+ ASSERT_EQ(on_finish, &finished_cond);
+
+ expect_get_object_size();
+ expect_get_parent_overlap(mock_image_ctx->layout.object_size);
+ expect_prune_parent_extents(mock_image_ctx->layout.object_size);
+ EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillRepeatedly(
+ Return(true));
+ EXPECT_CALL(*mock_image_ctx->object_map, object_may_exist(0)).WillOnce(
+ Return(false));
+ MockAbstractObjectWriteRequest *write_request = nullptr;
+ expect_copyup(&write_request, 0);
+
+ // unaligned write restarted
+ uint64_t version = 1234;
+ extents[0].bl.append(std::string(4096, '2'));
+ extents[1].bl.append(std::string(4096, '3'));
+ expect_object_read(&extents, version);
+ dispatcher_ctx->complete(-ENOENT); // complete first read
+ ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ auto expected_data =
+ std::string("2") + std::string(8192, '1') + std::string(4095, '3');
+ expect_object_write(0, expected_data, 0, std::make_optional(version));
+ dispatcher_ctx->complete(0); // 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;