From 02c8e8352ecaca9e65f25a188023e12184343410 Mon Sep 17 00:00:00 2001 From: Mykola Golub Date: Wed, 1 May 2019 11:25:20 +0100 Subject: [PATCH] cls/rbd: add sparse_copyup method Signed-off-by: Mykola Golub --- src/cls/rbd/cls_rbd.cc | 65 ++++++++++++++++++++++++++++++ src/cls/rbd/cls_rbd_client.cc | 18 +++++++++ src/cls/rbd/cls_rbd_client.h | 7 ++++ src/test/cls_rbd/test_cls_rbd.cc | 68 ++++++++++++++++++++++++++++++++ 4 files changed, 158 insertions(+) diff --git a/src/cls/rbd/cls_rbd.cc b/src/cls/rbd/cls_rbd.cc index 317cec69f68..8a3201c91f2 100644 --- a/src/cls/rbd/cls_rbd.cc +++ b/src/cls/rbd/cls_rbd.cc @@ -2694,6 +2694,67 @@ int copyup(cls_method_context_t hctx, bufferlist *in, bufferlist *out) return cls_cxx_write(hctx, 0, in->length(), in); } +/** + * Input: + * @param extent_map map of extents to write + * @param data bufferlist of data to write + * + * Output: + * @returns 0 on success, or if block already exists in child + * negative error code on other error + */ + +int sparse_copyup(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + std::map extent_map; + bufferlist data; + + try { + auto iter = in->cbegin(); + decode(extent_map, iter); + decode(data, iter); + } catch (const buffer::error &err) { + CLS_LOG(20, "sparse_copyup: invalid decode"); + return -EINVAL; + } + + int r = check_exists(hctx); + if (r == 0) { + return 0; + } + + if (extent_map.empty()) { + CLS_LOG(20, "sparse_copyup: create empty object"); + r = cls_cxx_create(hctx, true); + return r; + } + + uint64_t data_offset = 0; + for (auto &it: extent_map) { + auto off = it.first; + auto len = it.second; + + bufferlist tmpbl; + try { + tmpbl.substr_of(data, data_offset, len); + } catch (const buffer::error &err) { + CLS_LOG(20, "sparse_copyup: invalid data"); + return -EINVAL; + } + data_offset += len; + + CLS_LOG(20, "sparse_copyup: writing extent %" PRIu64 "~%" PRIu64 "\n", off, + len); + int r = cls_cxx_write(hctx, off, len, &tmpbl); + if (r < 0) { + CLS_ERR("sparse_copyup: error writing extent %" PRIu64 "~%" PRIu64 ": %s", + off, len, cpp_strerror(r).c_str()); + return r; + } + } + + return 0; +} /************************ rbd_id object methods **************************/ @@ -7549,6 +7610,7 @@ CLS_INIT(rbd) cls_method_handle_t h_namespace_remove; cls_method_handle_t h_namespace_list; cls_method_handle_t h_copyup; + cls_method_handle_t h_sparse_copyup; cls_method_handle_t h_assert_snapc_seq; cls_method_handle_t h_sparsify; @@ -7939,6 +8001,9 @@ CLS_INIT(rbd) cls_register_cxx_method(h_class, "copyup", CLS_METHOD_RD | CLS_METHOD_WR, copyup, &h_copyup); + cls_register_cxx_method(h_class, "sparse_copyup", + CLS_METHOD_RD | CLS_METHOD_WR, + sparse_copyup, &h_sparse_copyup); cls_register_cxx_method(h_class, "assert_snapc_seq", CLS_METHOD_RD | CLS_METHOD_WR, assert_snapc_seq, diff --git a/src/cls/rbd/cls_rbd_client.cc b/src/cls/rbd/cls_rbd_client.cc index ebf2104ad96..1e03fbce01b 100644 --- a/src/cls/rbd/cls_rbd_client.cc +++ b/src/cls/rbd/cls_rbd_client.cc @@ -839,6 +839,24 @@ int copyup(librados::IoCtx *ioctx, const std::string &oid, return ioctx->exec(oid, "rbd", "copyup", data, out); } +void sparse_copyup(librados::ObjectWriteOperation *op, + const std::map &extent_map, + bufferlist data) { + bufferlist bl; + encode(extent_map, bl); + encode(data, bl); + op->exec("rbd", "sparse_copyup", bl); +} + +int sparse_copyup(librados::IoCtx *ioctx, const std::string &oid, + const std::map &extent_map, + bufferlist data) { + librados::ObjectWriteOperation op; + sparse_copyup(&op, extent_map, data); + + return ioctx->operate(oid, &op); +} + void get_protection_status_start(librados::ObjectReadOperation *op, snapid_t snap_id) { diff --git a/src/cls/rbd/cls_rbd_client.h b/src/cls/rbd/cls_rbd_client.h index e03f0f410c1..d9a01acd110 100644 --- a/src/cls/rbd/cls_rbd_client.h +++ b/src/cls/rbd/cls_rbd_client.h @@ -600,6 +600,13 @@ void assert_snapc_seq(librados::ObjectWriteOperation *op, int copyup(librados::IoCtx *ioctx, const std::string &oid, bufferlist data); +void sparse_copyup(librados::ObjectWriteOperation *op, + const std::map &extent_map, + bufferlist data); +int sparse_copyup(librados::IoCtx *ioctx, const std::string &oid, + const std::map &extent_map, + bufferlist data); + void sparsify(librados::ObjectWriteOperation *op, size_t sparse_size, bool remove_empty); int sparsify(librados::IoCtx *ioctx, const std::string &oid, size_t sparse_size, diff --git a/src/test/cls_rbd/test_cls_rbd.cc b/src/test/cls_rbd/test_cls_rbd.cc index b3ee9e44a12..724bfa21ebf 100644 --- a/src/test/cls_rbd/test_cls_rbd.cc +++ b/src/test/cls_rbd/test_cls_rbd.cc @@ -179,6 +179,74 @@ TEST_F(TestClsRbd, copyup) ioctx.close(); } +TEST_F(TestClsRbd, sparse_copyup) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx)); + + string oid = get_temp_image_name(); + ioctx.remove(oid); + + bool sparse_read_supported = is_sparse_read_supported(ioctx, oid); + + // copyup of 0-len nonexistent object should create new 0-len object + uint64_t size; + ASSERT_EQ(-ENOENT, ioctx.stat(oid, &size, nullptr)); + std::map m; + bufferlist inbl; + ASSERT_EQ(0, sparse_copyup(&ioctx, oid, m, inbl)); + ASSERT_EQ(0, ioctx.stat(oid, &size, nullptr)); + ASSERT_EQ(0U, size); + + // create some data to write + inbl.append(std::string(4096, '1')); + inbl.append(std::string(4096, '2')); + m = {{1024, 4096}, {8192, 4096}}; + + // copyup to nonexistent object should create new object + ioctx.remove(oid); + ASSERT_EQ(-ENOENT, ioctx.remove(oid)); + ASSERT_EQ(0, sparse_copyup(&ioctx, oid, m, inbl)); + // and its contents should match + bufferlist outbl; + bufferlist expected_outbl; + expected_outbl.append(std::string(1024, '\0')); + expected_outbl.append(std::string(4096, '1')); + expected_outbl.append(std::string(8192 - 4096 - 1024, '\0')); + expected_outbl.append(std::string(4096, '2')); + ASSERT_EQ((int)expected_outbl.length(), + ioctx.read(oid, outbl, expected_outbl.length() + 1, 0)); + ASSERT_TRUE(outbl.contents_equal(expected_outbl)); + std::map expected_m; + if (sparse_read_supported) { + expected_m = m; + expected_outbl = inbl; + } else { + expected_m = {{0, expected_outbl.length()}}; + } + m.clear(); + outbl.clear(); + ASSERT_EQ((int)expected_m.size(), ioctx.sparse_read(oid, m, outbl, 65536, 0)); + ASSERT_EQ(m, expected_m); + ASSERT_TRUE(outbl.contents_equal(expected_outbl)); + + // now send different data, but with a preexisting object + bufferlist inbl2; + inbl2.append(std::string(1024, 'X')); + + // should still succeed + ASSERT_EQ(0, sparse_copyup(&ioctx, oid, {{0, 1024}}, inbl2)); + // but contents should not have changed + m.clear(); + outbl.clear(); + ASSERT_EQ((int)expected_m.size(), ioctx.sparse_read(oid, m, outbl, 65536, 0)); + ASSERT_EQ(m, expected_m); + ASSERT_TRUE(outbl.contents_equal(expected_outbl)); + + ASSERT_EQ(0, ioctx.remove(oid)); + ioctx.close(); +} + TEST_F(TestClsRbd, get_and_set_id) { librados::IoCtx ioctx; -- 2.39.5