diff options
Diffstat (limited to 'src/test/cls_cas')
-rw-r--r-- | src/test/cls_cas/CMakeLists.txt | 17 | ||||
-rw-r--r-- | src/test/cls_cas/test_cls_cas.cc | 368 |
2 files changed, 385 insertions, 0 deletions
diff --git a/src/test/cls_cas/CMakeLists.txt b/src/test/cls_cas/CMakeLists.txt new file mode 100644 index 000000000..16bff8b3b --- /dev/null +++ b/src/test/cls_cas/CMakeLists.txt @@ -0,0 +1,17 @@ +add_executable(ceph_test_cls_cas + test_cls_cas.cc) +target_link_libraries(ceph_test_cls_cas + librados + cls_cas_client + cls_cas_internal + global + radostest-cxx + ${UNITTEST_LIBS} + ${BLKID_LIBRARIES} + ${CMAKE_DL_LIBS} + ${CRYPTO_LIBS} + ${EXTRALIBS} + ) +install(TARGETS + ceph_test_cls_cas + DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/test/cls_cas/test_cls_cas.cc b/src/test/cls_cas/test_cls_cas.cc new file mode 100644 index 000000000..636c6e2db --- /dev/null +++ b/src/test/cls_cas/test_cls_cas.cc @@ -0,0 +1,368 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "include/types.h" +#include "include/stringify.h" +#include "cls/cas/cls_cas_client.h" +#include "cls/cas/cls_cas_internal.h" + +#include "include/utime.h" +#include "common/Clock.h" +#include "global/global_context.h" + +#include "gtest/gtest.h" +#include "test/librados/test_cxx.h" + +#include <errno.h> +#include <string> +#include <vector> + +using namespace std; + +/// creates a temporary pool and initializes an IoCtx for each test +class cls_cas : public ::testing::Test { + librados::Rados rados; + std::string pool_name; + protected: + librados::IoCtx ioctx; + + void SetUp() { + pool_name = get_temp_pool_name(); + /* create pool */ + ASSERT_EQ("", create_one_pool_pp(pool_name, rados)); + ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx)); + } + void TearDown() { + /* remove pool */ + ioctx.close(); + ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados)); + } +}; + +static librados::ObjectWriteOperation *new_op() { + return new librados::ObjectWriteOperation(); +} + +/* +static librados::ObjectReadOperation *new_rop() { + return new librados::ObjectReadOperation(); +} +*/ + +TEST_F(cls_cas, get_put) +{ + bufferlist bl; + bl.append("my data"); + string oid = "mychunk"; + hobject_t ref1, ref2, ref3; + ref1.oid.name = "foo1"; + ref2.oid.name = "foo2"; + ref3.oid.name = "foo3"; + + // initially the object does not exist + bufferlist t; + ASSERT_EQ(-ENOENT, ioctx.read(oid, t, 0, 0)); + + // write + { + auto op = new_op(); + cls_cas_chunk_create_or_get_ref(*op, ref1, bl); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + + // get x3 + { + auto op = new_op(); + cls_cas_chunk_get_ref(*op, ref2); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + + // get again + { + auto op = new_op(); + cls_cas_chunk_create_or_get_ref(*op, ref3, bl); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + + // 3x puts to remove + { + auto op = new_op(); + cls_cas_chunk_put_ref(*op, ref1); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + { + auto op = new_op(); + cls_cas_chunk_put_ref(*op, ref3); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + { + auto op = new_op(); + cls_cas_chunk_put_ref(*op, ref2); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(-ENOENT, ioctx.read(oid, t, 0, 0)); + + + // get + { + auto op = new_op(); + cls_cas_chunk_create_or_get_ref(*op, ref1, bl); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + + // put + { + auto op = new_op(); + cls_cas_chunk_put_ref(*op, ref1); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(-ENOENT, ioctx.read(oid, t, 0, 0)); +} + +TEST_F(cls_cas, wrong_put) +{ + bufferlist bl; + bl.append("my data"); + string oid = "mychunk"; + hobject_t ref1, ref2; + ref1.oid.name = "foo1"; + ref2.oid.name = "foo2"; + + // initially the object does not exist + bufferlist t; + ASSERT_EQ(-ENOENT, ioctx.read(oid, t, 0, 0)); + + // write + { + auto op = new_op(); + cls_cas_chunk_create_or_get_ref(*op, ref1, bl); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + + // put wrong thing + { + auto op = new_op(); + cls_cas_chunk_put_ref(*op, ref2); + ASSERT_EQ(-ENOLINK, ioctx.operate(oid, op)); + } + + { + auto op = new_op(); + cls_cas_chunk_put_ref(*op, ref1); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(-ENOENT, ioctx.read(oid, t, 0, 0)); +} + +TEST_F(cls_cas, dup_get) +{ + bufferlist bl; + bl.append("my data"); + string oid = "mychunk"; + hobject_t ref1; + ref1.oid.name = "foo1"; + + // initially the object does not exist + bufferlist t; + ASSERT_EQ(-ENOENT, ioctx.read(oid, t, 0, 0)); + + // duplicated entries are allowed by "by_object". as it tracks refs of the same + // hobject_t for snapshot support. + int n_refs = 0; + // write + { + auto op = new_op(); + cls_cas_chunk_create_or_get_ref(*op, ref1, bl); + ASSERT_EQ(0, ioctx.operate(oid, op)); + n_refs++; + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + + // dup create_or_get_ref, get_ref will succeed but take no additional ref + // only if the chunk_refs' type is not "by_object" + { + auto op = new_op(); + cls_cas_chunk_create_or_get_ref(*op, ref1, bl); + ASSERT_EQ(0, ioctx.operate(oid, op)); + n_refs++; + } + { + auto op = new_op(); + cls_cas_chunk_get_ref(*op, ref1); + ASSERT_EQ(0, ioctx.operate(oid, op)); + n_refs++; + } + + for (int i = 0; i < n_refs; i++) { + auto op = new_op(); + cls_cas_chunk_put_ref(*op, ref1); + ASSERT_EQ(0, ioctx.operate(oid, op)); + if (i < n_refs - 1) { + // should not referenced anymore, but by_object is an exception + // and by_object is used by default. + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + } else { + // the last reference was removed + ASSERT_EQ(-ENOENT, ioctx.read(oid, t, 0, 0)); + } + } +} + +TEST_F(cls_cas, dup_put) +{ + bufferlist bl; + bl.append("my data"); + string oid = "mychunk"; + hobject_t ref1; + ref1.oid.name = "foo1"; + + // initially the object does not exist + bufferlist t; + ASSERT_EQ(-ENOENT, ioctx.read(oid, t, 0, 0)); + + // write + { + auto op = new_op(); + cls_cas_chunk_create_or_get_ref(*op, ref1, bl); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + + { + auto op = new_op(); + cls_cas_chunk_put_ref(*op, ref1); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(-ENOENT, ioctx.read(oid, t, 0, 0)); + + { + auto op = new_op(); + cls_cas_chunk_put_ref(*op, ref1); + ASSERT_EQ(-ENOENT, ioctx.operate(oid, op)); + } +} + + +TEST_F(cls_cas, get_wrong_data) +{ + bufferlist bl, bl2; + bl.append("my data"); + bl2.append("my different data"); + string oid = "mychunk"; + hobject_t ref1, ref2; + ref1.oid.name = "foo1"; + ref2.oid.name = "foo2"; + + // initially the object does not exist + bufferlist t; + ASSERT_EQ(-ENOENT, ioctx.read(oid, t, 0, 0)); + + // get + { + auto op = new_op(); + cls_cas_chunk_create_or_get_ref(*op, ref1, bl); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + + // get w/ different data + // with verify + { + auto op = new_op(); + cls_cas_chunk_create_or_get_ref(*op, ref2, bl2, true); + ASSERT_EQ(-ENOMSG, ioctx.operate(oid, op)); + } + // without verify + { + auto op = new_op(); + cls_cas_chunk_create_or_get_ref(*op, ref2, bl2, false); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + + // put + { + auto op = new_op(); + cls_cas_chunk_put_ref(*op, ref1); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(bl.length(), ioctx.read(oid, t, 0, 0)); + { + auto op = new_op(); + cls_cas_chunk_put_ref(*op, ref2); + ASSERT_EQ(0, ioctx.operate(oid, op)); + } + ASSERT_EQ(-ENOENT, ioctx.read(oid, t, 0, 0)); +} + +static int count_bits(unsigned long n) +{ + // base case + if (n == 0) + return 0; + else + return 1 + count_bits(n & (n - 1)); +} + +TEST(chunk_refs_t, size) +{ + chunk_refs_t r; + size_t max = 1048576; + + // mix in pool changes as i gets bigger + size_t pool_mask = 0xfff5110; + + // eventually add in a zillion different pools to force us to a raw count + size_t pool_cutoff = max/2; + + for (size_t i = 1; i <= max; ++i) { + hobject_t h(sobject_t(object_t("foo"s + stringify(i)), i)); + h.pool = i > pool_cutoff ? i : (i & pool_mask); + r.get(h); + if (count_bits(i) <= 2) { + bufferlist bl; + r.dynamic_encode(bl, 512); + if (count_bits(i) == 1) { + cout << i << "\t" << bl.length() + << "\t" << r.describe_encoding() + << std::endl; + } + + // verify reencoding is correct + chunk_refs_t a; + auto t = bl.cbegin(); + decode(a, t); + bufferlist bl2; + encode(a, bl2); + if (!bl.contents_equal(bl2)) { + std::unique_ptr<Formatter> f(Formatter::create("json-pretty")); + cout << "original:\n"; + f->dump_object("refs", r); + f->flush(cout); + cout << "decoded:\n"; + f->dump_object("refs", a); + f->flush(cout); + cout << "original encoding:\n"; + bl.hexdump(cout); + cout << "decoded re-encoding:\n"; + bl2.hexdump(cout); + ASSERT_TRUE(bl.contents_equal(bl2)); + } + } + } + ASSERT_EQ(max, r.count()); + for (size_t i = 1; i <= max; ++i) { + hobject_t h(sobject_t(object_t("foo"s + stringify(i)), 1)); + h.pool = i > pool_cutoff ? i : (i & pool_mask); + bool ret = r.put(h); + ASSERT_TRUE(ret); + } + ASSERT_EQ(0, r.count()); +} |