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<uint64_t, uint64_t> 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 **************************/
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;
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,
return ioctx->exec(oid, "rbd", "copyup", data, out);
}
+void sparse_copyup(librados::ObjectWriteOperation *op,
+ const std::map<uint64_t, uint64_t> &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<uint64_t, uint64_t> &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)
{
int copyup(librados::IoCtx *ioctx, const std::string &oid,
bufferlist data);
+void sparse_copyup(librados::ObjectWriteOperation *op,
+ const std::map<uint64_t, uint64_t> &extent_map,
+ bufferlist data);
+int sparse_copyup(librados::IoCtx *ioctx, const std::string &oid,
+ const std::map<uint64_t, uint64_t> &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,
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<uint64_t, uint64_t> 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<uint64_t, uint64_t> 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;