X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=src%2Fceph%2Fsrc%2Ftest%2Flibrados%2Fmisc.cc;fp=src%2Fceph%2Fsrc%2Ftest%2Flibrados%2Fmisc.cc;h=f7ce348b0a0f863e77d95147709e053ac6aec4df;hb=812ff6ca9fcd3e629e49d4328905f33eee8ca3f5;hp=0000000000000000000000000000000000000000;hpb=15280273faafb77777eab341909a3f495cf248d9;p=stor4nfv.git diff --git a/src/ceph/src/test/librados/misc.cc b/src/ceph/src/test/librados/misc.cc new file mode 100644 index 0000000..f7ce348 --- /dev/null +++ b/src/ceph/src/test/librados/misc.cc @@ -0,0 +1,1380 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#include "gtest/gtest.h" + +#include "mds/mdstypes.h" +#include "include/err.h" +#include "include/buffer.h" +#include "include/rbd_types.h" +#include "include/rados/librados.h" +#include "include/rados/librados.hpp" +#include "include/stringify.h" +#include "common/Checksummer.h" +#include "global/global_context.h" +#include "test/librados/test.h" +#include "test/librados/TestCase.h" +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include + +using namespace librados; +using std::map; +using std::ostringstream; +using std::string; + +typedef RadosTest LibRadosMisc; +typedef RadosTestPP LibRadosMiscPP; +typedef RadosTestECPP LibRadosMiscECPP; + +TEST(LibRadosMiscVersion, Version) { + int major, minor, extra; + rados_version(&major, &minor, &extra); +} + +TEST(LibRadosMiscVersion, VersionPP) { + int major, minor, extra; + Rados::version(&major, &minor, &extra); +} + +static void test_rados_log_cb(void *arg, + const char *line, + const char *who, + uint64_t sec, uint64_t nsec, + uint64_t seq, const char *level, + const char *msg) +{ + std::cerr << "monitor log callback invoked" << std::endl; +} + +TEST(LibRadosMiscConnectFailure, ConnectFailure) { + rados_t cluster; + + char *id = getenv("CEPH_CLIENT_ID"); + if (id) + std::cerr << "Client id is: " << id << std::endl; + + ASSERT_EQ(0, rados_create(&cluster, NULL)); + ASSERT_EQ(0, rados_conf_read_file(cluster, NULL)); + ASSERT_EQ(0, rados_conf_parse_env(cluster, NULL)); + + ASSERT_EQ(0, rados_conf_set(cluster, "client_mount_timeout", "0.000000001")); + ASSERT_EQ(0, rados_conf_set(cluster, "debug_monc", "20")); + ASSERT_EQ(0, rados_conf_set(cluster, "debug_ms", "1")); + ASSERT_EQ(0, rados_conf_set(cluster, "log_to_stderr", "true")); + + ASSERT_EQ(-ENOTCONN, rados_monitor_log(cluster, "error", + test_rados_log_cb, NULL)); + + // try this a few times; sometimes we don't schedule fast enough for the + // cond to time out + int r; + for (unsigned i=0; i<16; ++i) { + cout << i << std::endl; + r = rados_connect(cluster); + if (r < 0) + break; // yay, we timed out + // try again + rados_shutdown(cluster); + ASSERT_EQ(0, rados_create(&cluster, NULL)); + } + ASSERT_NE(0, r); + + rados_shutdown(cluster); +} + +TEST(LibRadosMiscPool, PoolCreationRace) { + rados_t cluster_a, cluster_b; + + char *id = getenv("CEPH_CLIENT_ID"); + if (id) + std::cerr << "Client id is: " << id << std::endl; + + ASSERT_EQ(0, rados_create(&cluster_a, NULL)); + ASSERT_EQ(0, rados_conf_read_file(cluster_a, NULL)); + // kludge: i want to --log-file foo and only get cluster b + //ASSERT_EQ(0, rados_conf_parse_env(cluster_a, NULL)); + ASSERT_EQ(0, rados_connect(cluster_a)); + + ASSERT_EQ(0, rados_create(&cluster_b, NULL)); + ASSERT_EQ(0, rados_conf_read_file(cluster_b, NULL)); + ASSERT_EQ(0, rados_conf_parse_env(cluster_b, NULL)); + ASSERT_EQ(0, rados_conf_set(cluster_b, + "objecter_debug_inject_relock_delay", "true")); + ASSERT_EQ(0, rados_connect(cluster_b)); + + char poolname[80]; + snprintf(poolname, sizeof(poolname), "poolrace.%d", rand()); + rados_pool_create(cluster_a, poolname); + rados_ioctx_t a, b; + rados_ioctx_create(cluster_a, poolname, &a); + int64_t poolid = rados_ioctx_get_id(a); + + rados_ioctx_create2(cluster_b, poolid+1, &b); + + char pool2name[80]; + snprintf(pool2name, sizeof(pool2name), "poolrace2.%d", rand()); + rados_pool_create(cluster_a, pool2name); + + list cls; + // this should normally trigger pretty easily, but we need to bound + // the requests because if we get too many we'll get stuck by always + // sending enough messages that we hit the socket failure injection. + int max = 512; + while (max--) { + char buf[100]; + rados_completion_t c; + rados_aio_create_completion(0, 0, 0, &c); + cls.push_back(c); + rados_aio_read(b, "PoolCreationRaceObj", c, buf, 100, 0); + cout << "started " << (void*)c << std::endl; + if (rados_aio_is_complete(cls.front())) { + break; + } + } + while (!rados_aio_is_complete(cls.front())) { + cout << "waiting 1 sec" << std::endl; + sleep(1); + } + + cout << " started " << cls.size() << " aios" << std::endl; + for (auto c : cls) { + cout << "waiting " << (void*)c << std::endl; + rados_aio_wait_for_complete_and_cb(c); + rados_aio_release(c); + } + cout << "done." << std::endl; + + rados_ioctx_destroy(a); + rados_ioctx_destroy(b); + rados_pool_delete(cluster_a, poolname); + rados_pool_delete(cluster_a, pool2name); + rados_shutdown(cluster_b); + rados_shutdown(cluster_a); +} + +TEST_F(LibRadosMisc, ClusterFSID) { + char fsid[37]; + ASSERT_EQ(-ERANGE, rados_cluster_fsid(cluster, fsid, sizeof(fsid) - 1)); + ASSERT_EQ(sizeof(fsid) - 1, + (size_t)rados_cluster_fsid(cluster, fsid, sizeof(fsid))); +} + +TEST_F(LibRadosMiscPP, WaitOSDMapPP) { + ASSERT_EQ(0, cluster.wait_for_latest_osdmap()); +} + +TEST_F(LibRadosMiscPP, LongNamePP) { + bufferlist bl; + bl.append("content"); + int maxlen = g_conf->osd_max_object_name_len; + ASSERT_EQ(0, ioctx.write(string(maxlen/2, 'a').c_str(), bl, bl.length(), 0)); + ASSERT_EQ(0, ioctx.write(string(maxlen-1, 'a').c_str(), bl, bl.length(), 0)); + ASSERT_EQ(0, ioctx.write(string(maxlen, 'a').c_str(), bl, bl.length(), 0)); + ASSERT_EQ(-ENAMETOOLONG, ioctx.write(string(maxlen+1, 'a').c_str(), bl, bl.length(), 0)); + ASSERT_EQ(-ENAMETOOLONG, ioctx.write(string(maxlen*2, 'a').c_str(), bl, bl.length(), 0)); +} + +TEST_F(LibRadosMiscPP, LongLocatorPP) { + bufferlist bl; + bl.append("content"); + int maxlen = g_conf->osd_max_object_name_len; + ioctx.locator_set_key( + string((maxlen/2), 'a')); + ASSERT_EQ( + 0, + ioctx.write( + string("a").c_str(), + bl, bl.length(), 0)); + ioctx.locator_set_key( + string(maxlen - 1, 'a')); + ASSERT_EQ( + 0, + ioctx.write( + string("a").c_str(), + bl, bl.length(), 0)); + ioctx.locator_set_key( + string(maxlen, 'a')); + ASSERT_EQ( + 0, + ioctx.write( + string("a").c_str(), + bl, bl.length(), 0)); + ioctx.locator_set_key( + string(maxlen+1, 'a')); + ASSERT_EQ( + -ENAMETOOLONG, + ioctx.write( + string("a").c_str(), + bl, bl.length(), 0)); + ioctx.locator_set_key( + string((maxlen*2), 'a')); + ASSERT_EQ( + -ENAMETOOLONG, + ioctx.write( + string("a").c_str(), + bl, bl.length(), 0)); +} + +TEST_F(LibRadosMiscPP, LongNSpacePP) { + bufferlist bl; + bl.append("content"); + int maxlen = g_conf->osd_max_object_namespace_len; + ioctx.set_namespace( + string((maxlen/2), 'a')); + ASSERT_EQ( + 0, + ioctx.write( + string("a").c_str(), + bl, bl.length(), 0)); + ioctx.set_namespace( + string(maxlen - 1, 'a')); + ASSERT_EQ( + 0, + ioctx.write( + string("a").c_str(), + bl, bl.length(), 0)); + ioctx.set_namespace( + string(maxlen, 'a')); + ASSERT_EQ( + 0, + ioctx.write( + string("a").c_str(), + bl, bl.length(), 0)); + ioctx.set_namespace( + string(maxlen+1, 'a')); + ASSERT_EQ( + -ENAMETOOLONG, + ioctx.write( + string("a").c_str(), + bl, bl.length(), 0)); + ioctx.set_namespace( + string((maxlen*2), 'a')); + ASSERT_EQ( + -ENAMETOOLONG, + ioctx.write( + string("a").c_str(), + bl, bl.length(), 0)); +} + +TEST_F(LibRadosMiscPP, LongAttrNamePP) { + bufferlist bl; + bl.append("content"); + int maxlen = g_conf->osd_max_attr_name_len; + ASSERT_EQ(0, ioctx.setxattr("bigattrobj", string(maxlen/2, 'a').c_str(), bl)); + ASSERT_EQ(0, ioctx.setxattr("bigattrobj", string(maxlen-1, 'a').c_str(), bl)); + ASSERT_EQ(0, ioctx.setxattr("bigattrobj", string(maxlen, 'a').c_str(), bl)); + ASSERT_EQ(-ENAMETOOLONG, ioctx.setxattr("bigattrobj", string(maxlen+1, 'a').c_str(), bl)); + ASSERT_EQ(-ENAMETOOLONG, ioctx.setxattr("bigattrobj", string(maxlen*2, 'a').c_str(), bl)); +} + +static std::string read_key_from_tmap(IoCtx& ioctx, const std::string &obj, + const std::string &key) +{ + bufferlist bl; + int r = ioctx.read(obj, bl, 0, 0); + if (r <= 0) { + ostringstream oss; + oss << "ioctx.read(" << obj << ", bl, 0, 0) returned " << r; + return oss.str(); + } + bufferlist::iterator p = bl.begin(); + bufferlist header; + map m; + ::decode(header, p); + ::decode(m, p); + map::iterator i = m.find(key); + if (i == m.end()) + return ""; + std::string retstring; + ::decode(retstring, i->second); + return retstring; +} + +static std::string add_key_to_tmap(IoCtx &ioctx, const std::string &obj, + const std::string &key, const std::string &val) +{ + __u8 c = CEPH_OSD_TMAP_SET; + + bufferlist tmbl; + ::encode(c, tmbl); + ::encode(key, tmbl); + bufferlist blbl; + ::encode(val, blbl); + ::encode(blbl, tmbl); + int ret = ioctx.tmap_update(obj, tmbl); + if (ret) { + ostringstream oss; + oss << "ioctx.tmap_update(obj=" << obj << ", key=" + << key << ", val=" << val << ") failed with error " << ret; + return oss.str(); + } + return ""; +} + +static int remove_key_from_tmap(IoCtx &ioctx, const std::string &obj, + const std::string &key) +{ + __u8 c = CEPH_OSD_TMAP_RM; + + bufferlist tmbl; + ::encode(c, tmbl); + ::encode(key, tmbl); + int ret = ioctx.tmap_update(obj, tmbl); + if (ret) { + ostringstream oss; + oss << "ioctx.tmap_update(obj=" << obj << ", key=" + << key << ") failed with error " << ret; + } + return ret; +} + +TEST_F(LibRadosMiscPP, TmapUpdatePP) { + // create tmap + { + __u8 c = CEPH_OSD_TMAP_CREATE; + std::string my_tmap("my_tmap"); + bufferlist emptybl; + + bufferlist tmbl; + ::encode(c, tmbl); + ::encode(my_tmap, tmbl); + ::encode(emptybl, tmbl); + ASSERT_EQ(0, ioctx.tmap_update("foo", tmbl)); + } + + ASSERT_EQ(string(""), add_key_to_tmap(ioctx, "foo", "key1", "val1")); + + ASSERT_EQ(string(""), add_key_to_tmap(ioctx, "foo", "key2", "val2")); + + // read key1 from the tmap + ASSERT_EQ(string("val1"), read_key_from_tmap(ioctx, "foo", "key1")); + + // remove key1 from tmap + ASSERT_EQ(0, remove_key_from_tmap(ioctx, "foo", "key1")); + ASSERT_EQ(-ENOENT, remove_key_from_tmap(ioctx, "foo", "key1")); + + // key should be removed + ASSERT_EQ(string(""), read_key_from_tmap(ioctx, "foo", "key1")); +} + +TEST_F(LibRadosMiscPP, TmapUpdateMisorderedPP) { + // create tmap + { + __u8 c = CEPH_OSD_TMAP_CREATE; + std::string my_tmap("my_tmap"); + bufferlist emptybl; + + bufferlist tmbl; + ::encode(c, tmbl); + ::encode(my_tmap, tmbl); + ::encode(emptybl, tmbl); + ASSERT_EQ(0, ioctx.tmap_update("foo", tmbl)); + } + + // good update + { + __u8 c = CEPH_OSD_TMAP_SET; + bufferlist tmbl; + ::encode(c, tmbl); + ::encode("a", tmbl); + bufferlist blbl; + ::encode("old", blbl); + ::encode(blbl, tmbl); + + ::encode(c, tmbl); + ::encode("b", tmbl); + ::encode(blbl, tmbl); + + ::encode(c, tmbl); + ::encode("c", tmbl); + ::encode(blbl, tmbl); + + ASSERT_EQ(0, ioctx.tmap_update("foo", tmbl)); + } + + // bad update + { + __u8 c = CEPH_OSD_TMAP_SET; + bufferlist tmbl; + ::encode(c, tmbl); + ::encode("b", tmbl); + bufferlist blbl; + ::encode("new", blbl); + ::encode(blbl, tmbl); + + ::encode(c, tmbl); + ::encode("a", tmbl); + ::encode(blbl, tmbl); + + ::encode(c, tmbl); + ::encode("c", tmbl); + ::encode(blbl, tmbl); + + ASSERT_EQ(0, ioctx.tmap_update("foo", tmbl)); + } + + // check + ASSERT_EQ(string("new"), read_key_from_tmap(ioctx, "foo", "a")); + ASSERT_EQ(string("new"), read_key_from_tmap(ioctx, "foo", "b")); + ASSERT_EQ(string("new"), read_key_from_tmap(ioctx, "foo", "c")); + + ASSERT_EQ(0, remove_key_from_tmap(ioctx, "foo", "a")); + ASSERT_EQ(string(""), read_key_from_tmap(ioctx, "foo", "a")); + + ASSERT_EQ(0, remove_key_from_tmap(ioctx, "foo", "b")); + ASSERT_EQ(string(""), read_key_from_tmap(ioctx, "foo", "a")); +} + +TEST_F(LibRadosMiscPP, TmapUpdateMisorderedPutPP) { + // create unsorted tmap + string h("header"); + bufferlist bl; + ::encode(h, bl); + uint32_t n = 3; + ::encode(n, bl); + ::encode(string("b"), bl); + ::encode(string("bval"), bl); + ::encode(string("a"), bl); + ::encode(string("aval"), bl); + ::encode(string("c"), bl); + ::encode(string("cval"), bl); + bufferlist orig = bl; // tmap_put steals bl content + ASSERT_EQ(0, ioctx.tmap_put("foo", bl)); + + // check + bufferlist newbl; + ioctx.read("foo", newbl, orig.length(), 0); + ASSERT_EQ(orig.contents_equal(newbl), false); +} + +TEST_F(LibRadosMiscPP, Tmap2OmapPP) { + // create tmap + bufferlist hdr; + hdr.append("header"); + map omap; + omap["1"].append("a"); + omap["2"].append("b"); + omap["3"].append("c"); + { + bufferlist bl; + ::encode(hdr, bl); + ::encode(omap, bl); + ASSERT_EQ(0, ioctx.tmap_put("foo", bl)); + } + + // convert tmap to omap + ASSERT_EQ(0, ioctx.tmap_to_omap("foo", false)); + + // if tmap was truncated ? + { + uint64_t size; + time_t mtime; + ASSERT_EQ(0, ioctx.stat("foo", &size, &mtime)); + ASSERT_EQ(0U, size); + } + + // if 'nullok' works + ASSERT_EQ(0, ioctx.tmap_to_omap("foo", true)); + ASSERT_LE(ioctx.tmap_to_omap("foo", false), 0); + + { + // read omap + bufferlist got; + map m; + ObjectReadOperation o; + o.omap_get_header(&got, NULL); + o.omap_get_vals2("", 1024, &m, nullptr, nullptr); + ASSERT_EQ(0, ioctx.operate("foo", &o, NULL)); + + // compare header + ASSERT_TRUE(hdr.contents_equal(got)); + + // compare values + ASSERT_EQ(omap.size(), m.size()); + bool same = true; + for (map::iterator p = omap.begin(); p != omap.end(); ++p) { + map::iterator q = m.find(p->first); + if (q == m.end() || !p->second.contents_equal(q->second)) { + same = false; + break; + } + } + ASSERT_TRUE(same); + } +} + +TEST_F(LibRadosMisc, Exec) { + char buf[128]; + memset(buf, 0xcc, sizeof(buf)); + ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0)); + char buf2[512]; + int res = rados_exec(ioctx, "foo", "rbd", "get_all_features", + NULL, 0, buf2, sizeof(buf2)); + ASSERT_GT(res, 0); + bufferlist bl; + bl.append(buf2, res); + bufferlist::iterator iter = bl.begin(); + uint64_t all_features; + ::decode(all_features, iter); + // make sure *some* features are specified; don't care which ones + ASSERT_NE(all_features, (unsigned)0); +} + +TEST_F(LibRadosMiscPP, ExecPP) { + bufferlist bl; + ASSERT_EQ(0, ioctx.write("foo", bl, 0, 0)); + bufferlist bl2, out; + int r = ioctx.exec("foo", "rbd", "get_all_features", bl2, out); + ASSERT_EQ(0, r); + bufferlist::iterator iter = out.begin(); + uint64_t all_features; + ::decode(all_features, iter); + // make sure *some* features are specified; don't care which ones + ASSERT_NE(all_features, (unsigned)0); +} + +void set_completion_complete(rados_completion_t cb, void *arg) +{ + bool *my_aio_complete = (bool*)arg; + *my_aio_complete = true; +} + +TEST_F(LibRadosMiscPP, BadFlagsPP) { + unsigned badflags = CEPH_OSD_FLAG_PARALLELEXEC; + { + bufferlist bl; + bl.append("data"); + ASSERT_EQ(0, ioctx.write("badfoo", bl, bl.length(), 0)); + } + { + ASSERT_EQ(-EINVAL, ioctx.remove("badfoo", badflags)); + } +} + +TEST_F(LibRadosMiscPP, Operate1PP) { + ObjectWriteOperation o; + { + bufferlist bl; + o.write(0, bl); + } + std::string val1("val1"); + { + bufferlist bl; + bl.append(val1.c_str(), val1.size() + 1); + o.setxattr("key1", bl); + o.omap_clear(); // shouldn't affect attrs! + } + ASSERT_EQ(0, ioctx.operate("foo", &o)); + + ObjectWriteOperation empty; + ASSERT_EQ(0, ioctx.operate("foo", &empty)); + + { + bufferlist bl; + ASSERT_GT(ioctx.getxattr("foo", "key1", bl), 0); + ASSERT_EQ(0, strcmp(bl.c_str(), val1.c_str())); + } + ObjectWriteOperation o2; + { + bufferlist bl; + bl.append(val1); + o2.cmpxattr("key1", CEPH_OSD_CMPXATTR_OP_EQ, bl); + o2.rmxattr("key1"); + } + ASSERT_EQ(-ECANCELED, ioctx.operate("foo", &o2)); + ObjectWriteOperation o3; + { + bufferlist bl; + bl.append(val1); + o3.cmpxattr("key1", CEPH_OSD_CMPXATTR_OP_EQ, bl); + } + ASSERT_EQ(-ECANCELED, ioctx.operate("foo", &o3)); +} + +TEST_F(LibRadosMiscPP, Operate2PP) { + ObjectWriteOperation o; + { + bufferlist bl; + bl.append("abcdefg"); + o.write(0, bl); + } + std::string val1("val1"); + { + bufferlist bl; + bl.append(val1.c_str(), val1.size() + 1); + o.setxattr("key1", bl); + o.truncate(0); + } + ASSERT_EQ(0, ioctx.operate("foo", &o)); + uint64_t size; + time_t mtime; + ASSERT_EQ(0, ioctx.stat("foo", &size, &mtime)); + ASSERT_EQ(0U, size); +} + +TEST_F(LibRadosMiscPP, BigObjectPP) { + bufferlist bl; + bl.append("abcdefg"); + ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0)); + + { + ObjectWriteOperation o; + o.truncate(500000000000ull); + ASSERT_EQ(-EFBIG, ioctx.operate("foo", &o)); + } + { + ObjectWriteOperation o; + o.zero(500000000000ull, 1); + ASSERT_EQ(-EFBIG, ioctx.operate("foo", &o)); + } + { + ObjectWriteOperation o; + o.zero(1, 500000000000ull); + ASSERT_EQ(-EFBIG, ioctx.operate("foo", &o)); + } + { + ObjectWriteOperation o; + o.zero(500000000000ull, 500000000000ull); + ASSERT_EQ(-EFBIG, ioctx.operate("foo", &o)); + } + +#ifdef __LP64__ + // this test only works on 64-bit platforms + ASSERT_EQ(-EFBIG, ioctx.write("foo", bl, bl.length(), 500000000000ull)); +#endif +} + +TEST_F(LibRadosMiscPP, AioOperatePP) { + bool my_aio_complete = false; + AioCompletion *my_completion = cluster.aio_create_completion( + (void*)&my_aio_complete, set_completion_complete, NULL); + AioCompletion *my_completion_null = NULL; + ASSERT_NE(my_completion, my_completion_null); + + ObjectWriteOperation o; + { + bufferlist bl; + o.write(0, bl); + } + std::string val1("val1"); + { + bufferlist bl; + bl.append(val1.c_str(), val1.size() + 1); + o.setxattr("key1", bl); + bufferlist bl2; + char buf2[1024]; + memset(buf2, 0xdd, sizeof(buf2)); + bl2.append(buf2, sizeof(buf2)); + o.append(bl2); + } + ASSERT_EQ(0, ioctx.aio_operate("foo", my_completion, &o)); + ASSERT_EQ(0, my_completion->wait_for_complete_and_cb()); + ASSERT_EQ(my_aio_complete, true); + my_completion->release(); + + uint64_t size; + time_t mtime; + ASSERT_EQ(0, ioctx.stat("foo", &size, &mtime)); + ASSERT_EQ(1024U, size); +} + +TEST_F(LibRadosMiscPP, AssertExistsPP) { + char buf[64]; + memset(buf, 0xcc, sizeof(buf)); + bufferlist bl; + bl.append(buf, sizeof(buf)); + + ObjectWriteOperation op; + op.assert_exists(); + op.write(0, bl); + ASSERT_EQ(-ENOENT, ioctx.operate("asdffoo", &op)); + ASSERT_EQ(0, ioctx.create("asdffoo", true)); + ASSERT_EQ(0, ioctx.operate("asdffoo", &op)); + ASSERT_EQ(-EEXIST, ioctx.create("asdffoo", true)); +} + +TEST_F(LibRadosMiscPP, AssertVersionPP) { + char buf[64]; + memset(buf, 0xcc, sizeof(buf)); + bufferlist bl; + bl.append(buf, sizeof(buf)); + + // Create test object... + ASSERT_EQ(0, ioctx.create("asdfbar", true)); + // ...then write it again to guarantee that the + // (unsigned) version must be at least 1 (not 0) + // since we want to decrement it by 1 later. + ASSERT_EQ(0, ioctx.write_full("asdfbar", bl)); + + uint64_t v = ioctx.get_last_version(); + ObjectWriteOperation op1; + op1.assert_version(v+1); + op1.write(0, bl); + ASSERT_EQ(-EOVERFLOW, ioctx.operate("asdfbar", &op1)); + ObjectWriteOperation op2; + op2.assert_version(v-1); + op2.write(0, bl); + ASSERT_EQ(-ERANGE, ioctx.operate("asdfbar", &op2)); + ObjectWriteOperation op3; + op3.assert_version(v); + op3.write(0, bl); + ASSERT_EQ(0, ioctx.operate("asdfbar", &op3)); +} + +TEST_F(LibRadosMiscPP, BigAttrPP) { + char buf[64]; + memset(buf, 0xcc, sizeof(buf)); + bufferlist bl; + bl.append(buf, sizeof(buf)); + + ASSERT_EQ(0, ioctx.create("foo", true)); + + bufferlist got; + + cout << "osd_max_attr_size = " << g_conf->osd_max_attr_size << std::endl; + if (g_conf->osd_max_attr_size) { + bl.clear(); + got.clear(); + bl.append(buffer::create(g_conf->osd_max_attr_size)); + ASSERT_EQ(0, ioctx.setxattr("foo", "one", bl)); + ASSERT_EQ((int)bl.length(), ioctx.getxattr("foo", "one", got)); + ASSERT_TRUE(bl.contents_equal(got)); + + bl.clear(); + bl.append(buffer::create(g_conf->osd_max_attr_size+1)); + ASSERT_EQ(-EFBIG, ioctx.setxattr("foo", "one", bl)); + } else { + cout << "osd_max_attr_size == 0; skipping test" << std::endl; + } + + for (int i=0; i<1000; i++) { + bl.clear(); + got.clear(); + bl.append(buffer::create(MIN(g_conf->osd_max_attr_size, 1024))); + char n[10]; + snprintf(n, sizeof(n), "a%d", i); + ASSERT_EQ(0, ioctx.setxattr("foo", n, bl)); + ASSERT_EQ((int)bl.length(), ioctx.getxattr("foo", n, got)); + ASSERT_TRUE(bl.contents_equal(got)); + } +} + +TEST_F(LibRadosMiscPP, CopyPP) { + bufferlist bl, x; + bl.append("hi there"); + x.append("bar"); + + // small object + bufferlist blc = bl; + bufferlist xc = x; + ASSERT_EQ(0, ioctx.write_full("foo", blc)); + ASSERT_EQ(0, ioctx.setxattr("foo", "myattr", xc)); + + version_t uv = ioctx.get_last_version(); + { + // pass future version + ObjectWriteOperation op; + op.copy_from2("foo", ioctx, uv + 1, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + ASSERT_EQ(-EOVERFLOW, ioctx.operate("foo.copy", &op)); + } + { + // pass old version + ObjectWriteOperation op; + op.copy_from2("foo", ioctx, uv - 1, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + ASSERT_EQ(-ERANGE, ioctx.operate("foo.copy", &op)); + } + { + ObjectWriteOperation op; + op.copy_from2("foo", ioctx, uv, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + ASSERT_EQ(0, ioctx.operate("foo.copy", &op)); + + bufferlist bl2, x2; + ASSERT_EQ((int)bl.length(), ioctx.read("foo.copy", bl2, 10000, 0)); + ASSERT_TRUE(bl.contents_equal(bl2)); + ASSERT_EQ((int)x.length(), ioctx.getxattr("foo.copy", "myattr", x2)); + ASSERT_TRUE(x.contents_equal(x2)); + } + + // small object without a version + { + ObjectWriteOperation op; + op.copy_from2("foo", ioctx, 0, LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + ASSERT_EQ(0, ioctx.operate("foo.copy2", &op)); + + bufferlist bl2, x2; + ASSERT_EQ((int)bl.length(), ioctx.read("foo.copy2", bl2, 10000, 0)); + ASSERT_TRUE(bl.contents_equal(bl2)); + ASSERT_EQ((int)x.length(), ioctx.getxattr("foo.copy2", "myattr", x2)); + ASSERT_TRUE(x.contents_equal(x2)); + } + + // do a big object + bl.append(buffer::create(g_conf->osd_copyfrom_max_chunk * 3)); + bl.zero(); + bl.append("tail"); + blc = bl; + xc = x; + ASSERT_EQ(0, ioctx.write_full("big", blc)); + ASSERT_EQ(0, ioctx.setxattr("big", "myattr", xc)); + + { + ObjectWriteOperation op; + op.copy_from2("big", ioctx, ioctx.get_last_version(), LIBRADOS_OP_FLAG_FADVISE_DONTNEED); + ASSERT_EQ(0, ioctx.operate("big.copy", &op)); + + bufferlist bl2, x2; + ASSERT_EQ((int)bl.length(), ioctx.read("big.copy", bl2, bl.length(), 0)); + ASSERT_TRUE(bl.contents_equal(bl2)); + ASSERT_EQ((int)x.length(), ioctx.getxattr("foo.copy", "myattr", x2)); + ASSERT_TRUE(x.contents_equal(x2)); + } + + { + ObjectWriteOperation op; + op.copy_from2("big", ioctx, 0, LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL); + ASSERT_EQ(0, ioctx.operate("big.copy2", &op)); + + bufferlist bl2, x2; + ASSERT_EQ((int)bl.length(), ioctx.read("big.copy2", bl2, bl.length(), 0)); + ASSERT_TRUE(bl.contents_equal(bl2)); + ASSERT_EQ((int)x.length(), ioctx.getxattr("foo.copy2", "myattr", x2)); + ASSERT_TRUE(x.contents_equal(x2)); + } +} + +class LibRadosTwoPoolsECPP : public RadosTestECPP +{ +public: + LibRadosTwoPoolsECPP() {}; + ~LibRadosTwoPoolsECPP() override {}; +protected: + static void SetUpTestCase() { + pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_ec_pool_pp(pool_name, s_cluster)); + src_pool_name = get_temp_pool_name(); + ASSERT_EQ(0, s_cluster.pool_create(src_pool_name.c_str())); + + librados::IoCtx ioctx; + ASSERT_EQ(0, s_cluster.ioctx_create(pool_name.c_str(), ioctx)); + ioctx.application_enable("rados", true); + + librados::IoCtx src_ioctx; + ASSERT_EQ(0, s_cluster.ioctx_create(src_pool_name.c_str(), src_ioctx)); + src_ioctx.application_enable("rados", true); + } + static void TearDownTestCase() { + ASSERT_EQ(0, s_cluster.pool_delete(src_pool_name.c_str())); + ASSERT_EQ(0, destroy_one_ec_pool_pp(pool_name, s_cluster)); + } + static std::string src_pool_name; + + void SetUp() override { + RadosTestECPP::SetUp(); + ASSERT_EQ(0, cluster.ioctx_create(src_pool_name.c_str(), src_ioctx)); + src_ioctx.set_namespace(nspace); + } + void TearDown() override { + // wait for maps to settle before next test + cluster.wait_for_latest_osdmap(); + + RadosTestECPP::TearDown(); + + cleanup_default_namespace(src_ioctx); + cleanup_namespace(src_ioctx, nspace); + + src_ioctx.close(); + } + + librados::IoCtx src_ioctx; +}; +std::string LibRadosTwoPoolsECPP::src_pool_name; + +//copy_from between ecpool and no-ecpool. +TEST_F(LibRadosTwoPoolsECPP, CopyFrom) { + bufferlist z; + z.append_zero(4194304*2); + bufferlist b; + b.append("copyfrom"); + + // create big object w/ omapheader + { + ASSERT_EQ(0, src_ioctx.write_full("foo", z)); + ASSERT_EQ(0, src_ioctx.omap_set_header("foo", b)); + version_t uv = src_ioctx.get_last_version(); + ObjectWriteOperation op; + op.copy_from("foo", src_ioctx, uv); + ASSERT_EQ(-EOPNOTSUPP, ioctx.operate("foo.copy", &op)); + } + + // same with small object + { + ASSERT_EQ(0, src_ioctx.omap_set_header("bar", b)); + version_t uv = src_ioctx.get_last_version(); + ObjectWriteOperation op; + op.copy_from("bar", src_ioctx, uv); + ASSERT_EQ(-EOPNOTSUPP, ioctx.operate("bar.copy", &op)); + } +} + +TEST_F(LibRadosMiscPP, CopyScrubPP) { + bufferlist inbl, bl, x; + for (int i=0; i<100; ++i) + x.append("barrrrrrrrrrrrrrrrrrrrrrrrrr"); + bl.append(buffer::create(g_conf->osd_copyfrom_max_chunk * 3)); + bl.zero(); + bl.append("tail"); + bufferlist cbl; + + map to_set; + for (int i=0; i<1000; ++i) + to_set[string("foo") + stringify(i)] = x; + + // small + cbl = x; + ASSERT_EQ(0, ioctx.write_full("small", cbl)); + ASSERT_EQ(0, ioctx.setxattr("small", "myattr", x)); + + // big + cbl = bl; + ASSERT_EQ(0, ioctx.write_full("big", cbl)); + + // without header + cbl = bl; + ASSERT_EQ(0, ioctx.write_full("big2", cbl)); + ASSERT_EQ(0, ioctx.setxattr("big2", "myattr", x)); + ASSERT_EQ(0, ioctx.setxattr("big2", "myattr2", x)); + ASSERT_EQ(0, ioctx.omap_set("big2", to_set)); + + // with header + cbl = bl; + ASSERT_EQ(0, ioctx.write_full("big3", cbl)); + ASSERT_EQ(0, ioctx.omap_set_header("big3", x)); + ASSERT_EQ(0, ioctx.omap_set("big3", to_set)); + + // deep scrub to ensure digests are in place + { + for (int i=0; i<10; ++i) { + ostringstream ss; + ss << "{\"prefix\": \"pg deep-scrub\", \"pgid\": \"" + << ioctx.get_id() << "." << i + << "\"}"; + cluster.mon_command(ss.str(), inbl, NULL, NULL); + } + + // give it a few seconds to go. this is sloppy but is usually enough time + cout << "waiting for initial deep scrubs..." << std::endl; + sleep(30); + cout << "done waiting, doing copies" << std::endl; + } + + { + ObjectWriteOperation op; + op.copy_from("small", ioctx, 0); + ASSERT_EQ(0, ioctx.operate("small.copy", &op)); + } + + { + ObjectWriteOperation op; + op.copy_from("big", ioctx, 0); + ASSERT_EQ(0, ioctx.operate("big.copy", &op)); + } + + { + ObjectWriteOperation op; + op.copy_from("big2", ioctx, 0); + ASSERT_EQ(0, ioctx.operate("big2.copy", &op)); + } + + { + ObjectWriteOperation op; + op.copy_from("big3", ioctx, 0); + ASSERT_EQ(0, ioctx.operate("big3.copy", &op)); + } + + // deep scrub to ensure digests are correct + { + for (int i=0; i<10; ++i) { + ostringstream ss; + ss << "{\"prefix\": \"pg deep-scrub\", \"pgid\": \"" + << ioctx.get_id() << "." << i + << "\"}"; + cluster.mon_command(ss.str(), inbl, NULL, NULL); + } + + // give it a few seconds to go. this is sloppy but is usually enough time + cout << "waiting for final deep scrubs..." << std::endl; + sleep(30); + cout << "done waiting" << std::endl; + } +} + +TEST_F(LibRadosMiscPP, WriteSamePP) { + bufferlist bl; + char buf[128]; + bufferlist fl; + char full[128 * 4]; + char *cmp; + + /* zero the full range before using writesame */ + memset(full, 0, sizeof(full)); + fl.append(full, sizeof(full)); + ASSERT_EQ(0, ioctx.write("ws", fl, fl.length(), 0)); + + memset(buf, 0xcc, sizeof(buf)); + bl.clear(); + bl.append(buf, sizeof(buf)); + /* write the same buf four times */ + ASSERT_EQ(0, ioctx.writesame("ws", bl, sizeof(full), 0)); + + /* read back the full buffer and confirm that it matches */ + fl.clear(); + fl.append(full, sizeof(full)); + ASSERT_EQ((int)fl.length(), ioctx.read("ws", fl, fl.length(), 0)); + + for (cmp = fl.c_str(); cmp < fl.c_str() + fl.length(); cmp += sizeof(buf)) { + ASSERT_EQ(0, memcmp(cmp, buf, sizeof(buf))); + } + + /* write_len not a multiple of data_len should throw error */ + bl.clear(); + bl.append(buf, sizeof(buf)); + ASSERT_EQ(-EINVAL, ioctx.writesame("ws", bl, (sizeof(buf) * 4) - 1, 0)); + ASSERT_EQ(-EINVAL, + ioctx.writesame("ws", bl, bl.length() / 2, 0)); + /* write_len = data_len, i.e. same as write() */ + ASSERT_EQ(0, ioctx.writesame("ws", bl, sizeof(buf), 0)); + bl.clear(); + ASSERT_EQ(-EINVAL, + ioctx.writesame("ws", bl, sizeof(buf), 0)); +} + +TEST_F(LibRadosMisc, WriteSame) { + char buf[128]; + char full[128 * 4]; + char *cmp; + + /* zero the full range before using writesame */ + memset(full, 0, sizeof(full)); + ASSERT_EQ(0, rados_write(ioctx, "ws", full, sizeof(full), 0)); + + memset(buf, 0xcc, sizeof(buf)); + /* write the same buf four times */ + ASSERT_EQ(0, rados_writesame(ioctx, "ws", buf, sizeof(buf), sizeof(full), 0)); + + /* read back the full buffer and confirm that it matches */ + ASSERT_EQ((int)sizeof(full), rados_read(ioctx, "ws", full, sizeof(full), 0)); + + for (cmp = full; cmp < full + sizeof(full); cmp += sizeof(buf)) { + ASSERT_EQ(0, memcmp(cmp, buf, sizeof(buf))); + } + + /* write_len not a multiple of data_len should throw error */ + ASSERT_EQ(-EINVAL, rados_writesame(ioctx, "ws", buf, sizeof(buf), + (sizeof(buf) * 4) - 1, 0)); + ASSERT_EQ(-EINVAL, + rados_writesame(ioctx, "ws", buf, sizeof(buf), sizeof(buf) / 2, 0)); + ASSERT_EQ(-EINVAL, + rados_writesame(ioctx, "ws", buf, 0, sizeof(buf), 0)); + /* write_len = data_len, i.e. same as rados_write() */ + ASSERT_EQ(0, rados_writesame(ioctx, "ws", buf, sizeof(buf), sizeof(buf), 0)); +} + +template +class LibRadosChecksum : public LibRadosMiscPP { +public: + typedef typename T::alg_t alg_t; + typedef typename T::value_t value_t; + typedef typename alg_t::init_value_t init_value_t; + + static const rados_checksum_type_t type = T::type; + + bufferlist content_bl; + + using LibRadosMiscPP::SetUpTestCase; + using LibRadosMiscPP::TearDownTestCase; + + void SetUp() override { + LibRadosMiscPP::SetUp(); + + std::string content(4096, '\0'); + for (size_t i = 0; i < content.length(); ++i) { + content[i] = static_cast(rand() % (126 - 33) + 33); + } + content_bl.append(content); + ASSERT_EQ(0, ioctx.write("foo", content_bl, content_bl.length(), 0)); + } +}; + +template +class LibRadosChecksumParams { +public: + typedef AlgT alg_t; + typedef ValueT value_t; + static const rados_checksum_type_t type = _type; +}; + +typedef ::testing::Types< + LibRadosChecksumParams, + LibRadosChecksumParams, + LibRadosChecksumParams + > LibRadosChecksumTypes; + +TYPED_TEST_CASE(LibRadosChecksum, LibRadosChecksumTypes); + +TYPED_TEST(LibRadosChecksum, Subset) { + uint32_t chunk_size = 1024; + uint32_t csum_count = this->content_bl.length() / chunk_size; + + typename TestFixture::init_value_t init_value = -1; + bufferlist init_value_bl; + ::encode(init_value, init_value_bl); + + std::vector checksum_bls(csum_count); + std::vector checksum_rvals(csum_count); + + // individual checksum ops for each chunk + ObjectReadOperation op; + for (uint32_t i = 0; i < csum_count; ++i) { + op.checksum(TestFixture::type, init_value_bl, i * chunk_size, chunk_size, + 0, &checksum_bls[i], &checksum_rvals[i]); + } + ASSERT_EQ(0, this->ioctx.operate("foo", &op, NULL)); + + for (uint32_t i = 0; i < csum_count; ++i) { + ASSERT_EQ(0, checksum_rvals[i]); + + auto bl_it = checksum_bls[i].begin(); + uint32_t count; + ::decode(count, bl_it); + ASSERT_EQ(1U, count); + + typename TestFixture::value_t value; + ::decode(value, bl_it); + + bufferlist content_sub_bl; + content_sub_bl.substr_of(this->content_bl, i * chunk_size, chunk_size); + + typename TestFixture::value_t expected_value; + bufferptr expected_value_bp = buffer::create_static( + sizeof(expected_value), reinterpret_cast(&expected_value)); + Checksummer::template calculate( + init_value, chunk_size, 0, chunk_size, content_sub_bl, + &expected_value_bp); + ASSERT_EQ(expected_value, value); + } +} + +TYPED_TEST(LibRadosChecksum, Chunked) { + uint32_t chunk_size = 1024; + uint32_t csum_count = this->content_bl.length() / chunk_size; + + typename TestFixture::init_value_t init_value = -1; + bufferlist init_value_bl; + ::encode(init_value, init_value_bl); + + bufferlist checksum_bl; + int checksum_rval; + + // single op with chunked checksum results + ObjectReadOperation op; + op.checksum(TestFixture::type, init_value_bl, 0, this->content_bl.length(), + chunk_size, &checksum_bl, &checksum_rval); + ASSERT_EQ(0, this->ioctx.operate("foo", &op, NULL)); + ASSERT_EQ(0, checksum_rval); + + auto bl_it = checksum_bl.begin(); + uint32_t count; + ::decode(count, bl_it); + ASSERT_EQ(csum_count, count); + + std::vector expected_values(csum_count); + bufferptr expected_values_bp = buffer::create_static( + csum_count * sizeof(typename TestFixture::value_t), + reinterpret_cast(&expected_values[0])); + + Checksummer::template calculate( + init_value, chunk_size, 0, this->content_bl.length(), this->content_bl, + &expected_values_bp); + + for (uint32_t i = 0; i < csum_count; ++i) { + typename TestFixture::value_t value; + ::decode(value, bl_it); + ASSERT_EQ(expected_values[i], value); + } +} + +TEST_F(LibRadosMiscPP, CmpExtPP) { + bufferlist cmp_bl, bad_cmp_bl, write_bl; + char stored_str[] = "1234567891"; + char mismatch_str[] = "1234577777"; + + write_bl.append(stored_str); + ioctx.write("cmpextpp", write_bl, write_bl.length(), 0); + cmp_bl.append(stored_str); + ASSERT_EQ(0, ioctx.cmpext("cmpextpp", 0, cmp_bl)); + + bad_cmp_bl.append(mismatch_str); + ASSERT_EQ(-MAX_ERRNO - 5, ioctx.cmpext("cmpextpp", 0, bad_cmp_bl)); +} + +TEST_F(LibRadosMisc, CmpExt) { + bufferlist cmp_bl, bad_cmp_bl, write_bl; + char stored_str[] = "1234567891"; + char mismatch_str[] = "1234577777"; + + ASSERT_EQ(0, + rados_write(ioctx, "cmpextpp", stored_str, sizeof(stored_str), 0)); + + ASSERT_EQ(0, + rados_cmpext(ioctx, "cmpextpp", stored_str, sizeof(stored_str), 0)); + + ASSERT_EQ(-MAX_ERRNO - 5, + rados_cmpext(ioctx, "cmpextpp", mismatch_str, sizeof(mismatch_str), 0)); +} + +TEST_F(LibRadosMisc, Applications) { + const char *cmd[] = {"{\"prefix\":\"osd dump\"}", nullptr}; + char *buf, *st; + size_t buflen, stlen; + ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, + &buflen, &st, &stlen)); + ASSERT_LT(0u, buflen); + string result(buf); + rados_buffer_free(buf); + rados_buffer_free(st); + if (!boost::regex_search(result, boost::regex("require_osd_release [l-z]"))) { + std::cout << "SKIPPING"; + return; + } + + char apps[128]; + size_t app_len; + + app_len = sizeof(apps); + ASSERT_EQ(0, rados_application_list(ioctx, apps, &app_len)); + ASSERT_EQ(6U, app_len); + ASSERT_EQ(0, memcmp("rados\0", apps, app_len)); + + ASSERT_EQ(0, rados_application_enable(ioctx, "app1", 1)); + ASSERT_EQ(-EPERM, rados_application_enable(ioctx, "app2", 0)); + ASSERT_EQ(0, rados_application_enable(ioctx, "app2", 1)); + + ASSERT_EQ(-ERANGE, rados_application_list(ioctx, apps, &app_len)); + ASSERT_EQ(16U, app_len); + ASSERT_EQ(0, rados_application_list(ioctx, apps, &app_len)); + ASSERT_EQ(16U, app_len); + ASSERT_EQ(0, memcmp("app1\0app2\0rados\0", apps, app_len)); + + char keys[128]; + char vals[128]; + size_t key_len; + size_t val_len; + + key_len = sizeof(keys); + val_len = sizeof(vals); + ASSERT_EQ(-ENOENT, rados_application_metadata_list(ioctx, "dne", keys, + &key_len, vals, &val_len)); + ASSERT_EQ(0, rados_application_metadata_list(ioctx, "app1", keys, &key_len, + vals, &val_len)); + ASSERT_EQ(0U, key_len); + ASSERT_EQ(0U, val_len); + + ASSERT_EQ(-ENOENT, rados_application_metadata_set(ioctx, "dne", "key", + "value")); + ASSERT_EQ(0, rados_application_metadata_set(ioctx, "app1", "key1", "value1")); + ASSERT_EQ(0, rados_application_metadata_set(ioctx, "app1", "key2", "value2")); + + ASSERT_EQ(-ERANGE, rados_application_metadata_list(ioctx, "app1", keys, + &key_len, vals, &val_len)); + ASSERT_EQ(10U, key_len); + ASSERT_EQ(14U, val_len); + ASSERT_EQ(0, rados_application_metadata_list(ioctx, "app1", keys, &key_len, + vals, &val_len)); + ASSERT_EQ(10U, key_len); + ASSERT_EQ(14U, val_len); + ASSERT_EQ(0, memcmp("key1\0key2\0", keys, key_len)); + ASSERT_EQ(0, memcmp("value1\0value2\0", vals, val_len)); + + ASSERT_EQ(0, rados_application_metadata_remove(ioctx, "app1", "key1")); + ASSERT_EQ(0, rados_application_metadata_list(ioctx, "app1", keys, &key_len, + vals, &val_len)); + ASSERT_EQ(5U, key_len); + ASSERT_EQ(7U, val_len); + ASSERT_EQ(0, memcmp("key2\0", keys, key_len)); + ASSERT_EQ(0, memcmp("value2\0", vals, val_len)); +} + +TEST_F(LibRadosMiscPP, Applications) { + bufferlist inbl, outbl; + string outs; + ASSERT_EQ(0, cluster.mon_command("{\"prefix\": \"osd dump\"}", + inbl, &outbl, &outs)); + ASSERT_LT(0u, outbl.length()); + ASSERT_LE(0u, outs.length()); + if (!boost::regex_search(outbl.to_str(), + boost::regex("require_osd_release [l-z]"))) { + std::cout << "SKIPPING"; + return; + } + + std::set expected_apps = {"rados"}; + std::set apps; + ASSERT_EQ(0, ioctx.application_list(&apps)); + ASSERT_EQ(expected_apps, apps); + + ASSERT_EQ(0, ioctx.application_enable("app1", true)); + ASSERT_EQ(-EPERM, ioctx.application_enable("app2", false)); + ASSERT_EQ(0, ioctx.application_enable("app2", true)); + + expected_apps = {"app1", "app2", "rados"}; + ASSERT_EQ(0, ioctx.application_list(&apps)); + ASSERT_EQ(expected_apps, apps); + + std::map expected_meta; + std::map meta; + ASSERT_EQ(-ENOENT, ioctx.application_metadata_list("dne", &meta)); + ASSERT_EQ(0, ioctx.application_metadata_list("app1", &meta)); + ASSERT_EQ(expected_meta, meta); + + ASSERT_EQ(-ENOENT, ioctx.application_metadata_set("dne", "key1", "value1")); + ASSERT_EQ(0, ioctx.application_metadata_set("app1", "key1", "value1")); + ASSERT_EQ(0, ioctx.application_metadata_set("app1", "key2", "value2")); + + expected_meta = {{"key1", "value1"}, {"key2", "value2"}}; + ASSERT_EQ(0, ioctx.application_metadata_list("app1", &meta)); + ASSERT_EQ(expected_meta, meta); + + ASSERT_EQ(0, ioctx.application_metadata_remove("app1", "key1")); + + expected_meta = {{"key2", "value2"}}; + ASSERT_EQ(0, ioctx.application_metadata_list("app1", &meta)); + ASSERT_EQ(expected_meta, meta); +} + +TEST_F(LibRadosMiscECPP, CompareExtentRange) { + bufferlist bl1; + bl1.append("ceph"); + ObjectWriteOperation write; + write.write(0, bl1); + ASSERT_EQ(0, ioctx.operate("foo", &write)); + + bufferlist bl2; + bl2.append("ph"); + bl2.append(std::string(2, '\0')); + ObjectReadOperation read1; + read1.cmpext(2, bl2, nullptr); + ASSERT_EQ(0, ioctx.operate("foo", &read1, nullptr)); + + bufferlist bl3; + bl3.append(std::string(4, '\0')); + ObjectReadOperation read2; + read2.cmpext(2097152, bl3, nullptr); + ASSERT_EQ(0, ioctx.operate("foo", &read2, nullptr)); +}