From: Jonas Pfefferle Date: Fri, 5 Aug 2022 17:37:55 +0000 (+0200) Subject: librbd: make C++ cmp&write semantics equal to C API X-Git-Tag: v18.0.0~256^2~4 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=af96e6dae3f4c3e30f91cb26d6579a6b5a726862;p=ceph.git librbd: make C++ cmp&write semantics equal to C API The compare and write C++ API (both sync and async) does not check compare bufferlist length and executes compare ops of bufferlist length size, even if (write) "len" is smaller than bufferlist length. This patch changes this behavior by always issuing compares and writes of "len" size to match the C API. The bufferlist length can be greater than "len" for both compare and write but only "len" bytes are compared and written. If the bufferlist length is smaller than "len" the call will fail. Signed-off-by: Jonas Pfefferle --- diff --git a/src/include/rbd/librbd.hpp b/src/include/rbd/librbd.hpp index f1ddc2965e545..669f17a2cf50a 100644 --- a/src/include/rbd/librbd.hpp +++ b/src/include/rbd/librbd.hpp @@ -710,6 +710,27 @@ public: ssize_t writesame(uint64_t ofs, size_t len, ceph::bufferlist &bl, int op_flags); ssize_t write_zeroes(uint64_t ofs, size_t len, int zero_flags, int op_flags); + /** + * compare and write from/to image + * + * Compare data in compare bufferlist to data at offset in image. + * len bytes of the compare bufferlist are compared, i.e. the compare + * bufferlist has to be at least len bytes long. + * If the compare is successful len bytes from the write bufferlist + * are written to the image, i.e. the write bufferlist also has to be + * at least len bytes long. + * If the compare is unsuccessful no data is written and the + * offset in the bufferlist where the compare first differed + * is returned through mismatch_off. + * + * @param off offset in image + * @param len length of compare, length of write + * @param cmp_bl bufferlist to compare from + * @param bl bufferlist to write to image if compare succeeds + * @param c aio completion to notify when compare and write is complete + * @param mismatch_off (out) offset in bufferlist where compare first differed + * @param op_flags see librados.h constants beginning with LIBRADOS_OP_FLAG + */ ssize_t compare_and_write(uint64_t ofs, size_t len, ceph::bufferlist &cmp_bl, ceph::bufferlist& bl, uint64_t *mismatch_off, int op_flags); diff --git a/src/librbd/io/ImageRequest.cc b/src/librbd/io/ImageRequest.cc index aaf6e215edf6c..d00292e52d114 100644 --- a/src/librbd/io/ImageRequest.cc +++ b/src/librbd/io/ImageRequest.cc @@ -759,12 +759,17 @@ uint64_t ImageCompareAndWriteRequest::append_journal_event( template void ImageCompareAndWriteRequest::assemble_extent( - const LightweightObjectExtent &object_extent, bufferlist *bl) { + const LightweightObjectExtent &object_extent, bufferlist *bl, + bufferlist *cmp_bl) { for (auto q = object_extent.buffer_extents.begin(); q != object_extent.buffer_extents.end(); ++q) { bufferlist sub_bl; sub_bl.substr_of(m_bl, q->first, q->second); bl->claim_append(sub_bl); + + bufferlist sub_cmp_bl; + sub_cmp_bl.substr_of(m_cmp_bl, q->first, q->second); + cmp_bl->claim_append(sub_cmp_bl); } } @@ -774,13 +779,12 @@ ObjectDispatchSpec *ImageCompareAndWriteRequest::create_object_request( uint64_t journal_tid, bool single_extent, Context *on_finish) { I &image_ctx = this->m_image_ctx; - // NOTE: safe to move m_cmp_bl since we only support this op against - // a single object bufferlist bl; - assemble_extent(object_extent, &bl); + bufferlist cmp_bl; + assemble_extent(object_extent, &bl, &cmp_bl); auto req = ObjectDispatchSpec::create_compare_and_write( &image_ctx, OBJECT_DISPATCH_LAYER_NONE, object_extent.object_no, - object_extent.offset, std::move(m_cmp_bl), std::move(bl), io_context, + object_extent.offset, std::move(cmp_bl), std::move(bl), io_context, m_mismatch_offset, m_op_flags, journal_tid, this->m_trace, on_finish); return req; } diff --git a/src/librbd/io/ImageRequest.h b/src/librbd/io/ImageRequest.h index 2c05c3847f0d9..919a7381d378d 100644 --- a/src/librbd/io/ImageRequest.h +++ b/src/librbd/io/ImageRequest.h @@ -317,7 +317,7 @@ public: protected: void assemble_extent(const LightweightObjectExtent &object_extent, - bufferlist *bl); + bufferlist *bl, bufferlist *cmp_bl); ObjectDispatchSpec *create_object_request( const LightweightObjectExtent &object_extent, IOContext io_context, diff --git a/src/librbd/librbd.cc b/src/librbd/librbd.cc index 28bb2449d7520..1c99d20218085 100644 --- a/src/librbd/librbd.cc +++ b/src/librbd/librbd.cc @@ -2674,8 +2674,8 @@ namespace librbd { ictx->read_only, ofs, len, cmp_bl.length() < len ? NULL : cmp_bl.c_str(), bl.length() < len ? NULL : bl.c_str(), op_flags); - if (bl.length() < len) { - tracepoint(librbd, write_exit, -EINVAL); + if (bl.length() < len || cmp_bl.length() < len) { + tracepoint(librbd, compare_and_write_exit, -EINVAL); return -EINVAL; } @@ -2825,8 +2825,8 @@ namespace librbd { ictx->read_only, off, len, cmp_bl.length() < len ? NULL : cmp_bl.c_str(), bl.length() < len ? NULL : bl.c_str(), c->pc, op_flags); - if (bl.length() < len) { - tracepoint(librbd, compare_and_write_exit, -EINVAL); + if (bl.length() < len || cmp_bl.length() < len) { + tracepoint(librbd, aio_compare_and_write_exit, -EINVAL); return -EINVAL; } diff --git a/src/test/librbd/test_librbd.cc b/src/test/librbd/test_librbd.cc index 22e7bc02fcf9e..dab27295b0f72 100644 --- a/src/test/librbd/test_librbd.cc +++ b/src/test/librbd/test_librbd.cc @@ -3558,6 +3558,1033 @@ TEST_F(TestLibRBD, TestIOPP) ioctx.close(); } +static void compare_written(librbd::Image& image, off_t off, size_t len, + const std::string& buffer, bool *passed) +{ + bufferlist read_bl; + ssize_t read = image.read(off, len, read_bl); + ASSERT_EQ(len, read); + std::string read_buffer(read_bl.c_str(), read); + ASSERT_EQ(buffer.substr(0, len), read_buffer); + *passed = true; +} + +TEST_F(TestLibRBD, TestCompareAndWriteCompareTooSmallPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + std::string write_buffer("Write this !!!"); + ceph::bufferlist write_bl; + write_bl.append(&write_buffer[0], 6); + write_bl.append(&write_buffer[6], 5); + write_bl.append(&write_buffer[11], 3); + + ssize_t written = image.write(off, cmp_bl.length(), cmp_bl); + ASSERT_EQ(cmp_bl.length(), written); + + std::string small_buffer("Too small"); + ceph::bufferlist small_bl; + small_bl.append(&small_buffer[0], 4); + small_bl.append(&small_buffer[4], 4); + + // should fail because compare bufferlist cannot be smaller than len + uint64_t mismatch_off = 0; + written = image.compare_and_write(off, cmp_bl.length(), + small_bl, /* cmp_bl */ + write_bl, + &mismatch_off, 0); + ASSERT_EQ(-EINVAL, written); + ASSERT_EQ(0U, mismatch_off); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestAioCompareAndWriteCompareTooSmallPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + std::string write_buffer("Write this !!!"); + ceph::bufferlist write_bl; + write_bl.append(&write_buffer[0], 6); + write_bl.append(&write_buffer[6], 5); + write_bl.append(&write_buffer[11], 3); + + ssize_t written = image.write(off, cmp_bl.length(), cmp_bl); + ASSERT_EQ(cmp_bl.length(), written); + + std::string small_buffer("Too small"); + ceph::bufferlist small_bl; + small_bl.append(&small_buffer[0], 4); + small_bl.append(&small_buffer[4], 4); + + // should fail because compare bufferlist cannot be smaller than len + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion( + NULL, (librbd::callback_t) simple_write_cb_pp); + uint64_t mismatch_off = 0; + int ret = image.aio_compare_and_write(off, cmp_bl.length(), + small_bl, /* cmp_bl */ + write_bl, + comp, &mismatch_off, 0); + ASSERT_EQ(-EINVAL, ret); + ASSERT_EQ(0U, mismatch_off); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestCompareAndWriteWriteTooSmallPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + ssize_t written = image.write(off, cmp_bl.length(), cmp_bl); + ASSERT_EQ(cmp_bl.length(), written); + + std::string small_buffer("Too small"); + ceph::bufferlist small_bl; + small_bl.append(&small_buffer[0], 4); + small_bl.append(&small_buffer[4], 4); + + // should fail because write bufferlist cannot be smaller than len + uint64_t mismatch_off = 0; + written = image.compare_and_write(off, cmp_bl.length(), + cmp_bl, + small_bl, /* write_bl */ + &mismatch_off, 0); + ASSERT_EQ(-EINVAL, written); + ASSERT_EQ(0U, mismatch_off); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestAioCompareAndWriteWriteTooSmallPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + ssize_t written = image.write(off, cmp_bl.length(), cmp_bl); + ASSERT_EQ(cmp_bl.length(), written); + + std::string small_buffer("Too small"); + ceph::bufferlist small_bl; + small_bl.append(&small_buffer[0], 4); + small_bl.append(&small_buffer[4], 4); + + // should fail because write bufferlist cannot be smaller than len + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion( + NULL, (librbd::callback_t) simple_write_cb_pp); + uint64_t mismatch_off = 0; + int ret = image.aio_compare_and_write(off, cmp_bl.length(), + cmp_bl, + small_bl, /* write_bl */ + comp, &mismatch_off, 0); + ASSERT_EQ(-EINVAL, ret); + ASSERT_EQ(0U, mismatch_off); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestCompareAndWriteMismatchPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + std::string write_buffer("Write this !!!"); + ceph::bufferlist write_bl; + write_bl.append(&write_buffer[0], 6); + write_bl.append(&write_buffer[6], 5); + write_bl.append(&write_buffer[11], 3); + + std::string mismatch_buffer("This will fail"); + ceph::bufferlist mismatch_bl; + mismatch_bl.append(&mismatch_buffer[0], 5); + mismatch_bl.append(&mismatch_buffer[5], 5); + mismatch_bl.append(&mismatch_buffer[10], 4); + + ssize_t written = image.write(off, cmp_bl.length(), cmp_bl); + ASSERT_EQ(cmp_bl.length(), written); + + // this should execute the compare but fail because of mismatch + uint64_t mismatch_off = 0; + written = image.compare_and_write(off, write_bl.length(), + mismatch_bl, /* cmp_bl */ + write_bl, + &mismatch_off, 0); + ASSERT_EQ(-EILSEQ, written); + ASSERT_EQ(5U, mismatch_off); + + // check that nothing was written + ASSERT_PASSED(compare_written, image, off, cmp_bl.length(), cmp_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestAioCompareAndWriteMismatchPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + std::string write_buffer("Write this !!!"); + ceph::bufferlist write_bl; + write_bl.append(&write_buffer[0], 6); + write_bl.append(&write_buffer[6], 5); + write_bl.append(&write_buffer[11], 3); + + std::string mismatch_buffer("This will fail"); + ceph::bufferlist mismatch_bl; + mismatch_bl.append(&mismatch_buffer[0], 5); + mismatch_bl.append(&mismatch_buffer[5], 5); + mismatch_bl.append(&mismatch_buffer[10], 4); + + ssize_t written = image.write(off, cmp_bl.length(), cmp_bl); + ASSERT_EQ(cmp_bl.length(), written); + + // this should execute the compare but fail because of mismatch + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion( + NULL, (librbd::callback_t) simple_write_cb_pp); + uint64_t mismatch_off = 0; + int ret = image.aio_compare_and_write(off, write_bl.length(), + mismatch_bl, /* cmp_bl */ + write_bl, + comp, &mismatch_off, 0); + ASSERT_EQ(0, ret); + comp->wait_for_complete(); + ssize_t aio_ret = comp->get_return_value(); + ASSERT_EQ(-EILSEQ, aio_ret); + ASSERT_EQ(5U, mismatch_off); + comp->release(); + + // check that nothing was written + ASSERT_PASSED(compare_written, image, off, cmp_bl.length(), cmp_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestCompareAndWriteMismatchBufferlistGreaterLenPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + std::string write_buffer("Write this !!!"); + ceph::bufferlist write_bl; + write_bl.append(&write_buffer[0], 6); + write_bl.append(&write_buffer[6], 5); + write_bl.append(&write_buffer[11], 3); + + std::string mismatch_buffer("This will fail"); + ceph::bufferlist mismatch_bl; + mismatch_bl.append(&mismatch_buffer[0], 5); + mismatch_bl.append(&mismatch_buffer[5], 5); + mismatch_bl.append(&mismatch_buffer[10], 4); + + ssize_t written = image.write(off, cmp_bl.length(), cmp_bl); + ASSERT_EQ(cmp_bl.length(), written); + + /* + * we allow cmp_bl and write_bl to be greater than len so this + * should execute the compare but fail because of mismatch + */ + uint64_t mismatch_off = 0; + written = image.compare_and_write(off, cmp_bl.length() - 1, + mismatch_bl, /* cmp_bl */ + write_bl, + &mismatch_off, 0); + ASSERT_EQ(-EILSEQ, written); + ASSERT_EQ(5U, mismatch_off); + + // check that nothing was written + ASSERT_PASSED(compare_written, image, off, cmp_bl.length(), cmp_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestAioCompareAndWriteMismatchBufferlistGreaterLenPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + std::string write_buffer("Write this !!!"); + ceph::bufferlist write_bl; + write_bl.append(&write_buffer[0], 6); + write_bl.append(&write_buffer[6], 5); + write_bl.append(&write_buffer[11], 3); + + std::string mismatch_buffer("This will fail"); + ceph::bufferlist mismatch_bl; + mismatch_bl.append(&mismatch_buffer[0], 5); + mismatch_bl.append(&mismatch_buffer[5], 5); + mismatch_bl.append(&mismatch_buffer[10], 4); + + ssize_t written = image.write(off, cmp_bl.length(), cmp_bl); + ASSERT_EQ(cmp_bl.length(), written); + + /* + * we allow cmp_bl and write_bl to be greater than len so this + * should execute the compare but fail because of mismatch + */ + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion( + NULL, (librbd::callback_t) simple_write_cb_pp); + uint64_t mismatch_off = 0; + int ret = image.aio_compare_and_write(off, cmp_bl.length() - 1, + mismatch_bl, /* cmp_bl */ + write_bl, + comp, &mismatch_off, 0); + ASSERT_EQ(0, ret); + comp->wait_for_complete(); + ssize_t aio_ret = comp->get_return_value(); + ASSERT_EQ(-EILSEQ, aio_ret); + ASSERT_EQ(5U, mismatch_off); + comp->release(); + + // check that nothing was written + ASSERT_PASSED(compare_written, image, off, cmp_bl.length(), cmp_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestCompareAndWriteSuccessPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + std::string write_buffer("Write this !!!"); + ceph::bufferlist write_bl; + write_bl.append(&write_buffer[0], 6); + write_bl.append(&write_buffer[6], 5); + write_bl.append(&write_buffer[11], 3); + + ssize_t written = image.write(off, cmp_bl.length(), cmp_bl); + ASSERT_EQ(cmp_bl.length(), written); + + // compare against the buffer written before => should succeed + uint64_t mismatch_off = 0; + written = image.compare_and_write(off, cmp_bl.length(), + cmp_bl, + write_bl, + &mismatch_off, 0); + ASSERT_EQ(write_bl.length(), written); + ASSERT_EQ(0U, mismatch_off); + + // check write_bl was written + ASSERT_PASSED(compare_written, image, off, write_bl.length(), write_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestAioCompareAndWriteSuccessPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + std::string write_buffer("Write this !!!"); + ceph::bufferlist write_bl; + write_bl.append(&write_buffer[0], 6); + write_bl.append(&write_buffer[6], 5); + write_bl.append(&write_buffer[11], 3); + + ssize_t written = image.write(off, cmp_bl.length(), cmp_bl); + ASSERT_EQ(cmp_bl.length(), written); + + // compare against the buffer written before => should succeed + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion( + NULL, (librbd::callback_t) simple_write_cb_pp); + uint64_t mismatch_off = 0; + int ret = image.aio_compare_and_write(off, write_bl.length(), + cmp_bl, + write_bl, + comp, &mismatch_off, 0); + ASSERT_EQ(0, ret); + comp->wait_for_complete(); + ssize_t aio_ret = comp->get_return_value(); + ASSERT_EQ(0, aio_ret); + ASSERT_EQ(0U, mismatch_off); + comp->release(); + + // check write_bl was written + ASSERT_PASSED(compare_written, image, off, write_bl.length(), write_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestCompareAndWriteSuccessBufferlistGreaterLenPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + std::string write_buffer("Write this !!!"); + ceph::bufferlist write_bl; + write_bl.append(&write_buffer[0], 6); + write_bl.append(&write_buffer[6], 5); + write_bl.append(&write_buffer[11], 3); + + std::string mismatch_buffer("This will fail"); + ceph::bufferlist mismatch_bl; + mismatch_bl.append(&mismatch_buffer[0], 5); + mismatch_bl.append(&mismatch_buffer[5], 5); + mismatch_bl.append(&mismatch_buffer[10], 4); + + /* + * Test len < cmp_bl & write_bl => should succeed but only compare + * len bytes resp. only write len bytes + */ + ssize_t written = image.write(off, mismatch_bl.length(), mismatch_bl); + ASSERT_EQ(mismatch_bl.length(), written); + + size_t len_m1 = cmp_bl.length() - 1; + written = image.write(off, len_m1, cmp_bl); + ASSERT_EQ(len_m1, written); + // the content of the image at off should now be "This is a tesl" + + uint64_t mismatch_off = 0; + written = image.compare_and_write(off, len_m1, + cmp_bl, + write_bl, + &mismatch_off, 0); + ASSERT_EQ(len_m1, written); + ASSERT_EQ(0U, mismatch_off); + + // check that only write_bl.length() - 1 bytes were written + ASSERT_PASSED(compare_written, image, off, len_m1, write_buffer); + ASSERT_PASSED(compare_written, image, off + len_m1, 1, std::string("l")); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestAioCompareAndWriteSuccessBufferlistGreaterLenPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + off_t off = 512; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + std::string cmp_buffer("This is a test"); + ceph::bufferlist cmp_bl; + cmp_bl.append(&cmp_buffer[0], 5); + cmp_bl.append(&cmp_buffer[5], 3); + cmp_bl.append(&cmp_buffer[8], 2); + cmp_bl.append(&cmp_buffer[10], 4); + + std::string write_buffer("Write this !!!"); + ceph::bufferlist write_bl; + write_bl.append(&write_buffer[0], 6); + write_bl.append(&write_buffer[6], 5); + write_bl.append(&write_buffer[11], 3); + + std::string mismatch_buffer("This will fail"); + ceph::bufferlist mismatch_bl; + mismatch_bl.append(&mismatch_buffer[0], 5); + mismatch_bl.append(&mismatch_buffer[5], 5); + mismatch_bl.append(&mismatch_buffer[10], 4); + + ssize_t written = image.write(off, cmp_bl.length(), cmp_bl); + ASSERT_EQ(cmp_bl.length(), written); + + /* + * Test len < cmp_bl & write_bl => should succeed but only compare + * len bytes resp. only write len bytes + */ + written = image.write(off, mismatch_bl.length(), mismatch_bl); + ASSERT_EQ(mismatch_bl.length(), written); + + size_t len_m1 = cmp_bl.length() - 1; + written = image.write(off, len_m1, cmp_bl); + ASSERT_EQ(len_m1, written); + // the content of the image at off should now be "This is a tesl" + + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion( + NULL, (librbd::callback_t) simple_write_cb_pp); + uint64_t mismatch_off = 0; + int ret = image.aio_compare_and_write(off, len_m1, + cmp_bl, + write_bl, + comp, &mismatch_off, 0); + ASSERT_EQ(0, ret); + comp->wait_for_complete(); + ssize_t aio_ret = comp->get_return_value(); + ASSERT_EQ(0, aio_ret); + ASSERT_EQ(0U, mismatch_off); + comp->release(); + + // check that only write_bl.length() - 1 bytes were written + ASSERT_PASSED(compare_written, image, off, len_m1, write_buffer); + ASSERT_PASSED(compare_written, image, off + len_m1, 1, std::string("l")); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestCompareAndWriteStripeUnitUnalignedPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + // large write test => we allow stripe unit size writes (aligned) + uint64_t stripe_unit = image.get_stripe_unit(); + std::string large_write_buffer(stripe_unit, '2'); + ceph::bufferlist large_write_bl; + large_write_bl.append(large_write_buffer.data(), + large_write_buffer.length()); + + std::string large_cmp_buffer(stripe_unit * 2, '3'); + ceph::bufferlist large_cmp_bl; + large_cmp_bl.append(large_cmp_buffer.data(), large_cmp_buffer.length()); + + ssize_t written = image.write(stripe_unit, large_cmp_bl.length(), + large_cmp_bl); + ASSERT_EQ(large_cmp_bl.length(), written); + + /* + * compare and write at offset stripe_unit + 1 and stripe unit size + * Expect fail because access exceeds stripe + */ + uint64_t mismatch_off = 0; + written = image.compare_and_write(stripe_unit + 1, stripe_unit, + large_cmp_bl, + large_write_bl, + &mismatch_off, 0); + ASSERT_EQ(-EINVAL, written); + ASSERT_EQ(0U, mismatch_off); + + // check nothing has been written + ASSERT_PASSED(compare_written, image, stripe_unit, large_cmp_bl.length(), + large_cmp_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestAioCompareAndWriteStripeUnitUnalignedPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + // large write test => we allow stripe unit size writes (aligned) + uint64_t stripe_unit = image.get_stripe_unit(); + std::string large_write_buffer(stripe_unit, '2'); + ceph::bufferlist large_write_bl; + large_write_bl.append(large_write_buffer.data(), + large_write_buffer.length()); + + std::string large_cmp_buffer(stripe_unit * 2, '3'); + ceph::bufferlist large_cmp_bl; + large_cmp_bl.append(large_cmp_buffer.data(), large_cmp_buffer.length()); + + ssize_t written = image.write(stripe_unit, large_cmp_bl.length(), + large_cmp_bl); + ASSERT_EQ(large_cmp_bl.length(), written); + + /* + * compare and write at offset stripe_unit + 1 and stripe unit size + * Expect fail because access exceeds stripe + */ + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion( + NULL, (librbd::callback_t) simple_write_cb_pp); + uint64_t mismatch_off = 0; + int ret = image.aio_compare_and_write(stripe_unit + 1, stripe_unit, + large_cmp_bl, + large_write_bl, + comp, &mismatch_off, 0); + ASSERT_EQ(0, ret); + comp->wait_for_complete(); + ssize_t aio_ret = comp->get_return_value(); + ASSERT_EQ(-EINVAL, aio_ret); + ASSERT_EQ(0U, mismatch_off); + comp->release(); + + // check nothing has been written + ASSERT_PASSED(compare_written, image, stripe_unit, large_cmp_bl.length(), + large_cmp_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestCompareAndWriteTooLargePP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + // large write test => we allow stripe unit size writes (aligned) + uint64_t stripe_unit = image.get_stripe_unit(); + std::string large_write_buffer(stripe_unit * 2, '2'); + ceph::bufferlist large_write_bl; + large_write_bl.append(large_write_buffer.data(), + large_write_buffer.length()); + + std::string large_cmp_buffer(stripe_unit * 2, '3'); + ceph::bufferlist large_cmp_bl; + large_cmp_bl.append(large_cmp_buffer.data(), large_cmp_buffer.length()); + + ssize_t written = image.write(stripe_unit, large_cmp_bl.length(), + large_cmp_bl); + ASSERT_EQ(large_cmp_bl.length(), written); + + /* + * compare and write at offset stripe_unit and stripe unit size + 1 + * Expect fail because access is larger than stripe unit size + */ + uint64_t mismatch_off = 0; + written = image.compare_and_write(stripe_unit, stripe_unit + 1, + large_cmp_bl, + large_write_bl, + &mismatch_off, 0); + ASSERT_EQ(-EINVAL, written); + ASSERT_EQ(0U, mismatch_off); + + // check nothing has been written + ASSERT_PASSED(compare_written, image, stripe_unit, large_cmp_bl.length(), + large_cmp_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestAioCompareAndWriteTooLargePP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + // large write test => we allow stripe unit size writes (aligned) + uint64_t stripe_unit = image.get_stripe_unit(); + std::string large_write_buffer(stripe_unit * 2, '2'); + ceph::bufferlist large_write_bl; + large_write_bl.append(large_write_buffer.data(), + large_write_buffer.length()); + + std::string large_cmp_buffer(stripe_unit * 2, '3'); + ceph::bufferlist large_cmp_bl; + large_cmp_bl.append(large_cmp_buffer.data(), large_cmp_buffer.length()); + + ssize_t written = image.write(stripe_unit, large_cmp_bl.length(), + large_cmp_bl); + ASSERT_EQ(large_cmp_bl.length(), written); + + /* + * compare and write at offset stripe_unit and stripe unit size + 1 + * Expect fail because access is larger than stripe unit size + */ + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion( + NULL, (librbd::callback_t) simple_write_cb_pp); + uint64_t mismatch_off = 0; + int ret = image.aio_compare_and_write(stripe_unit, stripe_unit + 1, + large_cmp_bl, + large_write_bl, + comp, &mismatch_off, 0); + ASSERT_EQ(0, ret); + comp->wait_for_complete(); + ssize_t aio_ret = comp->get_return_value(); + ASSERT_EQ(-EINVAL, aio_ret); + ASSERT_EQ(0U, mismatch_off); + comp->release(); + + // check nothing has been written + ASSERT_PASSED(compare_written, image, stripe_unit, large_cmp_bl.length(), + large_cmp_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestCompareAndWriteStripeUnitSuccessPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + // large write test => we allow stripe unit size writes (aligned) + uint64_t stripe_unit = image.get_stripe_unit(); + std::string large_write_buffer(stripe_unit * 2, '2'); + ceph::bufferlist large_write_bl; + large_write_bl.append(large_write_buffer.data(), + large_write_buffer.length()); + + std::string large_cmp_buffer(stripe_unit * 2, '3'); + ceph::bufferlist large_cmp_bl; + large_cmp_bl.append(large_cmp_buffer.data(), large_cmp_buffer.length()); + + ssize_t written = image.write(stripe_unit, large_cmp_bl.length(), + large_cmp_bl); + ASSERT_EQ(large_cmp_bl.length(), written); + + // aligned stripe unit size access => expect success + uint64_t mismatch_off = 0; + written = image.compare_and_write(stripe_unit, stripe_unit, + large_cmp_bl, + large_write_bl, + &mismatch_off, 0); + ASSERT_EQ(stripe_unit, written); + ASSERT_EQ(0U, mismatch_off); + + // check large_write_bl was written and nothing beyond + ASSERT_PASSED(compare_written, image, stripe_unit, stripe_unit, + large_write_buffer); + ASSERT_PASSED(compare_written, image, stripe_unit * 2, stripe_unit, + large_cmp_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + +TEST_F(TestLibRBD, TestAioCompareAndWriteStripeUnitSuccessPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 20 << 20; /* 20MiB */ + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + // large write test => we allow stripe unit size writes (aligned) + uint64_t stripe_unit = image.get_stripe_unit(); + std::string large_write_buffer(stripe_unit * 2, '2'); + ceph::bufferlist large_write_bl; + large_write_bl.append(large_write_buffer.data(), + large_write_buffer.length()); + + std::string large_cmp_buffer(stripe_unit * 2, '3'); + ceph::bufferlist large_cmp_bl; + large_cmp_bl.append(large_cmp_buffer.data(), + large_cmp_buffer.length()); + + ssize_t written = image.write(stripe_unit, large_cmp_bl.length(), + large_cmp_bl); + ASSERT_EQ(large_cmp_bl.length(), written); + + // aligned stripe unit size access => expect success + librbd::RBD::AioCompletion *comp = new librbd::RBD::AioCompletion( + NULL, (librbd::callback_t) simple_write_cb_pp); + uint64_t mismatch_off = 0; + int ret = image.aio_compare_and_write(stripe_unit, stripe_unit, + large_cmp_bl, + large_write_bl, + comp, &mismatch_off, 0); + ASSERT_EQ(0, ret); + comp->wait_for_complete(); + ssize_t aio_ret = comp->get_return_value(); + ASSERT_EQ(0, aio_ret); + ASSERT_EQ(0U, mismatch_off); + comp->release(); + + // check large_write_bl was written and nothing beyond + ASSERT_PASSED(compare_written, image, stripe_unit, stripe_unit, + large_write_buffer); + ASSERT_PASSED(compare_written, image, stripe_unit * 2, stripe_unit, + large_cmp_buffer); + + ASSERT_PASSED(validate_object_map, image); + } + + ioctx.close(); +} + TEST_F(TestLibRBD, TestIOPPWithIOHint) { librados::IoCtx ioctx; @@ -5638,14 +6665,14 @@ void compare_and_write_copyup(librados::IoCtx &ioctx, bool deep_copyup, } bufferlist cmp_bl; - cmp_bl.append(std::string(96, '1')); + cmp_bl.append(std::string(512, '1')); bufferlist write_bl; write_bl.append(std::string(512, '2')); - uint64_t mismatch_off; + uint64_t mismatch_off = 0; ASSERT_EQ((ssize_t)write_bl.length(), clone_image.compare_and_write(512, write_bl.length(), cmp_bl, write_bl, &mismatch_off, 0)); - + ASSERT_EQ(0U, mismatch_off); bufferlist read_bl; ASSERT_EQ(4096, clone_image.read(0, 4096, read_bl)); @@ -5702,10 +6729,10 @@ void compare_and_write_copyup_mismatch(librados::IoCtx &ioctx, bufferlist cmp_bl; cmp_bl.append(std::string(48, '1')); - cmp_bl.append(std::string(48, '3')); + cmp_bl.append(std::string(464, '3')); bufferlist write_bl; write_bl.append(std::string(512, '2')); - uint64_t mismatch_off; + uint64_t mismatch_off = 0; ASSERT_EQ(-EILSEQ, clone_image.compare_and_write(512, write_bl.length(), cmp_bl, write_bl, &mismatch_off, 0));