summaryrefslogtreecommitdiffstats
path: root/src/test/librados
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/test/librados/CMakeLists.txt217
-rw-r--r--src/test/librados/TestCase.cc203
-rw-r--r--src/test/librados/TestCase.h124
-rw-r--r--src/test/librados/aio.cc1724
-rw-r--r--src/test/librados/aio_cxx.cc2467
-rw-r--r--src/test/librados/asio.cc369
-rw-r--r--src/test/librados/c_read_operations.cc895
-rw-r--r--src/test/librados/c_write_operations.cc279
-rw-r--r--src/test/librados/cls.cc36
-rw-r--r--src/test/librados/cls_remote_reads.cc55
-rw-r--r--src/test/librados/cmd.cc229
-rw-r--r--src/test/librados/cmd_cxx.cc92
-rw-r--r--src/test/librados/completion_speed.cc38
-rw-r--r--src/test/librados/crimson_utils.h15
-rw-r--r--src/test/librados/io.cc461
-rw-r--r--src/test/librados/io_cxx.cc986
-rw-r--r--src/test/librados/librados.cc13
-rw-r--r--src/test/librados/librados_config.cc98
-rw-r--r--src/test/librados/list.cc555
-rw-r--r--src/test/librados/list_cxx.cc782
-rw-r--r--src/test/librados/lock.cc237
-rw-r--r--src/test/librados/lock_cxx.cc203
-rw-r--r--src/test/librados/misc.cc358
-rw-r--r--src/test/librados/misc_cxx.cc923
-rw-r--r--src/test/librados/op_speed.cc24
-rw-r--r--src/test/librados/pool.cc186
-rw-r--r--src/test/librados/service.cc209
-rw-r--r--src/test/librados/service_cxx.cc105
-rw-r--r--src/test/librados/snapshots.cc356
-rw-r--r--src/test/librados/snapshots_cxx.cc790
-rw-r--r--src/test/librados/snapshots_stats.cc332
-rw-r--r--src/test/librados/snapshots_stats_cxx.cc324
-rw-r--r--src/test/librados/stat.cc153
-rw-r--r--src/test/librados/stat_cxx.cc168
-rw-r--r--src/test/librados/test.cc198
-rw-r--r--src/test/librados/test.h32
-rw-r--r--src/test/librados/test_common.cc167
-rw-r--r--src/test/librados/test_common.h9
-rw-r--r--src/test/librados/test_cxx.cc203
-rw-r--r--src/test/librados/test_cxx.h19
-rw-r--r--src/test/librados/test_shared.cc44
-rw-r--r--src/test/librados/test_shared.h58
-rw-r--r--src/test/librados/testcase_cxx.cc407
-rw-r--r--src/test/librados/testcase_cxx.h130
-rw-r--r--src/test/librados/tier_cxx.cc9304
-rw-r--r--src/test/librados/watch_notify.cc657
-rw-r--r--src/test/librados/watch_notify_cxx.cc416
-rw-r--r--src/test/librados_test_stub/CMakeLists.txt12
-rw-r--r--src/test/librados_test_stub/LibradosTestStub.cc1568
-rw-r--r--src/test/librados_test_stub/LibradosTestStub.h42
-rw-r--r--src/test/librados_test_stub/MockTestMemCluster.h36
-rw-r--r--src/test/librados_test_stub/MockTestMemIoCtxImpl.h252
-rw-r--r--src/test/librados_test_stub/MockTestMemRadosClient.h103
-rw-r--r--src/test/librados_test_stub/NeoradosTestStub.cc601
-rw-r--r--src/test/librados_test_stub/TestClassHandler.cc159
-rw-r--r--src/test/librados_test_stub/TestClassHandler.h79
-rw-r--r--src/test/librados_test_stub/TestCluster.h64
-rw-r--r--src/test/librados_test_stub/TestIoCtxImpl.cc394
-rw-r--r--src/test/librados_test_stub/TestIoCtxImpl.h221
-rw-r--r--src/test/librados_test_stub/TestMemCluster.cc203
-rw-r--r--src/test/librados_test_stub/TestMemCluster.h124
-rw-r--r--src/test/librados_test_stub/TestMemIoCtxImpl.cc924
-rw-r--r--src/test/librados_test_stub/TestMemIoCtxImpl.h104
-rw-r--r--src/test/librados_test_stub/TestMemRadosClient.cc118
-rw-r--r--src/test/librados_test_stub/TestMemRadosClient.h88
-rw-r--r--src/test/librados_test_stub/TestRadosClient.cc311
-rw-r--r--src/test/librados_test_stub/TestRadosClient.h162
-rw-r--r--src/test/librados_test_stub/TestWatchNotify.cc459
-rw-r--r--src/test/librados_test_stub/TestWatchNotify.h148
-rw-r--r--src/test/libradosstriper/CMakeLists.txt34
-rw-r--r--src/test/libradosstriper/TestCase.cc80
-rw-r--r--src/test/libradosstriper/TestCase.h82
-rw-r--r--src/test/libradosstriper/aio.cc581
-rw-r--r--src/test/libradosstriper/io.cc430
-rw-r--r--src/test/libradosstriper/striping.cc329
75 files changed, 33358 insertions, 0 deletions
diff --git a/src/test/librados/CMakeLists.txt b/src/test/librados/CMakeLists.txt
new file mode 100644
index 000000000..5d5623f06
--- /dev/null
+++ b/src/test/librados/CMakeLists.txt
@@ -0,0 +1,217 @@
+# radostest
+add_library(radostest_shared OBJECT test_shared.cc)
+target_include_directories(radostest_shared PRIVATE
+ $<TARGET_PROPERTY:GTest::GTest,INTERFACE_INCLUDE_DIRECTORIES>)
+
+add_library(radostest STATIC
+ test_common.cc
+ TestCase.cc
+ test.cc
+ $<TARGET_OBJECTS:radostest_shared>)
+target_link_libraries(radostest PUBLIC
+ GTest::GTest
+ ceph-common
+ json_spirit
+ ${GSSAPI_LIBRARIES} ${EXTRALIBS})
+add_library(radostest-cxx STATIC
+ testcase_cxx.cc
+ test_cxx.cc
+ $<TARGET_OBJECTS:radostest_shared>)
+target_link_libraries(radostest-cxx PUBLIC
+ GTest::GTest
+ ceph-common)
+
+add_executable(ceph_test_rados_api_cmd
+ cmd.cc)
+target_link_libraries(ceph_test_rados_api_cmd
+ librados ${UNITTEST_LIBS} radostest)
+add_executable(ceph_test_rados_api_cmd_pp
+ cmd_cxx.cc)
+target_link_libraries(ceph_test_rados_api_cmd_pp
+ librados ${UNITTEST_LIBS} radostest-cxx)
+
+add_executable(ceph_test_rados_api_io
+ io.cc)
+target_link_libraries(ceph_test_rados_api_io
+ librados ${UNITTEST_LIBS} radostest)
+add_executable(ceph_test_rados_api_io_pp
+ io_cxx.cc)
+target_link_libraries(ceph_test_rados_api_io_pp
+ librados ${UNITTEST_LIBS} radostest-cxx)
+
+add_executable(ceph_test_rados_api_c_write_operations
+ c_write_operations.cc)
+target_link_libraries(ceph_test_rados_api_c_write_operations
+ librados ${UNITTEST_LIBS} radostest)
+
+add_executable(ceph_test_rados_api_c_read_operations
+ c_read_operations.cc)
+target_link_libraries(ceph_test_rados_api_c_read_operations
+ librados ${UNITTEST_LIBS} radostest)
+
+add_executable(ceph_test_rados_api_aio
+ aio.cc)
+target_link_libraries(ceph_test_rados_api_aio
+ librados ${UNITTEST_LIBS} radostest)
+add_executable(ceph_test_rados_api_aio_pp
+ aio_cxx.cc)
+target_link_libraries(ceph_test_rados_api_aio_pp
+ librados ${UNITTEST_LIBS} radostest-cxx)
+
+add_executable(ceph_test_rados_api_asio asio.cc)
+target_link_libraries(ceph_test_rados_api_asio global
+ librados ${UNITTEST_LIBS} spawn)
+
+add_executable(ceph_test_rados_api_list
+ list.cc
+ $<TARGET_OBJECTS:unit-main>)
+target_link_libraries(ceph_test_rados_api_list
+ librados global ${UNITTEST_LIBS} radostest)
+
+add_executable(ceph_test_rados_api_pool
+ pool.cc)
+target_link_libraries(ceph_test_rados_api_pool
+ librados ${UNITTEST_LIBS} radostest)
+
+add_executable(ceph_test_rados_api_stat
+ stat.cc)
+target_link_libraries(ceph_test_rados_api_stat
+ librados ${UNITTEST_LIBS} radostest)
+add_executable(ceph_test_rados_api_stat_pp
+ stat_cxx.cc)
+target_link_libraries(ceph_test_rados_api_stat_pp
+ librados ${UNITTEST_LIBS} radostest-cxx)
+
+add_executable(ceph_test_rados_api_watch_notify
+ watch_notify.cc)
+target_link_libraries(ceph_test_rados_api_watch_notify
+ librados ${UNITTEST_LIBS} radostest)
+add_executable(ceph_test_rados_api_watch_notify_pp
+ watch_notify_cxx.cc)
+target_link_libraries(ceph_test_rados_api_watch_notify_pp
+ librados ${UNITTEST_LIBS} radostest-cxx)
+
+add_executable(ceph_test_rados_api_cls
+ cls.cc)
+target_link_libraries(ceph_test_rados_api_cls
+ librados ${UNITTEST_LIBS} radostest-cxx)
+
+add_executable(ceph_test_rados_api_misc
+ misc.cc
+ $<TARGET_OBJECTS:unit-main>)
+target_link_libraries(ceph_test_rados_api_misc
+ librados global ${UNITTEST_LIBS} radostest)
+add_executable(ceph_test_rados_api_misc_pp
+ misc_cxx.cc
+ $<TARGET_OBJECTS:unit-main>)
+target_link_libraries(ceph_test_rados_api_misc_pp
+ librados global ${UNITTEST_LIBS} radostest-cxx)
+
+add_executable(ceph_test_rados_api_lock
+ lock.cc)
+target_link_libraries(ceph_test_rados_api_lock
+ librados ${UNITTEST_LIBS} radostest)
+add_executable(ceph_test_rados_api_lock_pp
+ lock_cxx.cc)
+target_link_libraries(ceph_test_rados_api_lock_pp
+ librados ${UNITTEST_LIBS} radostest-cxx)
+
+add_executable(ceph_test_rados_api_service
+ service.cc)
+target_link_libraries(ceph_test_rados_api_service
+ librados global ${UNITTEST_LIBS} radostest)
+add_executable(ceph_test_rados_api_service_pp
+ service_cxx.cc)
+target_link_libraries(ceph_test_rados_api_service_pp
+ librados global ${UNITTEST_LIBS} radostest-cxx)
+
+add_executable(ceph_test_rados_api_tier_pp
+ tier_cxx.cc
+ $<TARGET_OBJECTS:unit-main>)
+target_include_directories(ceph_test_rados_api_tier_pp
+ PUBLIC "${CMAKE_SOURCE_DIR}/src/rgw/driver/rados"
+ PUBLIC "${CMAKE_SOURCE_DIR}/src/rgw")
+target_link_libraries(ceph_test_rados_api_tier_pp
+ librados global ${UNITTEST_LIBS} Boost::system radostest-cxx cls_cas_internal
+ cls_cas_client spawn)
+
+add_executable(ceph_test_rados_api_snapshots
+ snapshots.cc)
+target_link_libraries(ceph_test_rados_api_snapshots
+ librados ${UNITTEST_LIBS} radostest)
+add_executable(ceph_test_rados_api_snapshots_pp
+ snapshots_cxx.cc)
+target_link_libraries(ceph_test_rados_api_snapshots_pp
+ librados ${UNITTEST_LIBS} radostest-cxx)
+add_executable(ceph_test_rados_api_snapshots_stats
+ snapshots_stats.cc)
+target_link_libraries(ceph_test_rados_api_snapshots_stats
+ librados ${UNITTEST_LIBS} radostest)
+add_executable(ceph_test_rados_api_snapshots_stats_pp
+ snapshots_stats_cxx.cc)
+target_link_libraries(ceph_test_rados_api_snapshots_stats_pp
+ librados ${UNITTEST_LIBS} radostest-cxx)
+
+add_executable(ceph_test_rados_api_cls_remote_reads
+ cls_remote_reads.cc
+ $<TARGET_OBJECTS:unit-main>)
+target_link_libraries(ceph_test_rados_api_cls_remote_reads
+ librados global ${UNITTEST_LIBS} radostest-cxx)
+
+install(TARGETS
+ ceph_test_rados_api_aio
+ ceph_test_rados_api_aio_pp
+ ceph_test_rados_api_asio
+ ceph_test_rados_api_c_read_operations
+ ceph_test_rados_api_c_write_operations
+ ceph_test_rados_api_cmd
+ ceph_test_rados_api_cmd_pp
+ ceph_test_rados_api_io
+ ceph_test_rados_api_io_pp
+ ceph_test_rados_api_list
+ ceph_test_rados_api_lock
+ ceph_test_rados_api_lock_pp
+ ceph_test_rados_api_misc
+ ceph_test_rados_api_misc_pp
+ ceph_test_rados_api_pool
+ ceph_test_rados_api_service
+ ceph_test_rados_api_service_pp
+ ceph_test_rados_api_snapshots
+ ceph_test_rados_api_snapshots_pp
+ ceph_test_rados_api_stat
+ ceph_test_rados_api_stat_pp
+ ceph_test_rados_api_tier_pp
+ ceph_test_rados_api_watch_notify
+ ceph_test_rados_api_watch_notify_pp
+ ceph_test_rados_api_cls_remote_reads
+ DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+# unittest_librados
+add_executable(unittest_librados
+ librados.cc
+ )
+add_ceph_unittest(unittest_librados)
+target_link_libraries(unittest_librados librados ${BLKID_LIBRARIES}
+ ${GSSAPI_LIBRARIES})
+
+# unittest_librados_config
+add_executable(unittest_librados_config
+ librados_config.cc
+ )
+add_ceph_unittest(unittest_librados_config)
+target_link_libraries(unittest_librados_config
+ librados
+ ${BLKID_LIBRARIES} ${GSSAPI_LIBRARIES})
+
+# Removing this test. We can't shove it into Finisher as it's not a
+# Context any more, and wrapping it to adapt it would be less fair.
+
+#add_executable(ceph_test_rados_completion_speed
+# completion_speed.cc)
+#target_link_libraries(ceph_test_rados_completion_speed
+# librados ${UNITTEST_LIBS} radostest-cxx)
+
+add_executable(ceph_test_rados_op_speed
+ op_speed.cc)
+target_link_libraries(ceph_test_rados_op_speed
+ librados ${UNITTEST_LIBS} radostest-cxx)
diff --git a/src/test/librados/TestCase.cc b/src/test/librados/TestCase.cc
new file mode 100644
index 000000000..e99c19273
--- /dev/null
+++ b/src/test/librados/TestCase.cc
@@ -0,0 +1,203 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <errno.h>
+#include <fmt/format.h>
+#include "test/librados/test.h"
+#include "test/librados/TestCase.h"
+#include "include/scope_guard.h"
+#include "crimson_utils.h"
+
+std::string RadosTestNS::pool_name;
+rados_t RadosTestNS::s_cluster = NULL;
+
+
+void RadosTestNS::SetUpTestCase()
+{
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool(pool_name, &s_cluster));
+}
+
+void RadosTestNS::TearDownTestCase()
+{
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &s_cluster));
+}
+
+void RadosTestNS::SetUp()
+{
+ cluster = RadosTestNS::s_cluster;
+ ASSERT_EQ(0, rados_ioctx_create(cluster, pool_name.c_str(), &ioctx));
+ int req;
+ ASSERT_EQ(0, rados_ioctx_pool_requires_alignment2(ioctx, &req));
+ ASSERT_FALSE(req);
+}
+
+void RadosTestNS::TearDown()
+{
+ if (cleanup)
+ cleanup_all_objects(ioctx);
+ rados_ioctx_destroy(ioctx);
+}
+
+void RadosTestNS::cleanup_all_objects(rados_ioctx_t ioctx)
+{
+ // remove all objects to avoid polluting other tests
+ rados_ioctx_snap_set_read(ioctx, LIBRADOS_SNAP_HEAD);
+ rados_ioctx_set_namespace(ioctx, LIBRADOS_ALL_NSPACES);
+ rados_list_ctx_t list_ctx;
+
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &list_ctx));
+ auto sg = make_scope_guard([&] { rados_nobjects_list_close(list_ctx); });
+
+ int r;
+ const char *entry = NULL;
+ const char *key = NULL;
+ const char *nspace = NULL;
+ while ((r = rados_nobjects_list_next(list_ctx, &entry, &key, &nspace)) != -ENOENT) {
+ ASSERT_EQ(0, r);
+ rados_ioctx_locator_set_key(ioctx, key);
+ rados_ioctx_set_namespace(ioctx, nspace);
+ ASSERT_EQ(0, rados_remove(ioctx, entry));
+ }
+}
+
+std::string RadosTestECNS::pool_name;
+rados_t RadosTestECNS::s_cluster = NULL;
+
+void RadosTestECNS::SetUpTestCase()
+{
+ SKIP_IF_CRIMSON();
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_ec_pool(pool_name, &s_cluster));
+}
+
+void RadosTestECNS::TearDownTestCase()
+{
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, destroy_one_ec_pool(pool_name, &s_cluster));
+}
+
+void RadosTestECNS::SetUp()
+{
+ SKIP_IF_CRIMSON();
+ cluster = RadosTestECNS::s_cluster;
+ ASSERT_EQ(0, rados_ioctx_create(cluster, pool_name.c_str(), &ioctx));
+ int req;
+ ASSERT_EQ(0, rados_ioctx_pool_requires_alignment2(ioctx, &req));
+ ASSERT_TRUE(req);
+ ASSERT_EQ(0, rados_ioctx_pool_required_alignment2(ioctx, &alignment));
+ ASSERT_NE(0U, alignment);
+}
+
+void RadosTestECNS::TearDown()
+{
+ SKIP_IF_CRIMSON();
+ if (cleanup)
+ cleanup_all_objects(ioctx);
+ rados_ioctx_destroy(ioctx);
+}
+
+std::string RadosTest::pool_name;
+rados_t RadosTest::s_cluster = NULL;
+
+void RadosTest::SetUpTestCase()
+{
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool(pool_name, &s_cluster));
+}
+
+void RadosTest::TearDownTestCase()
+{
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &s_cluster));
+}
+
+void RadosTest::SetUp()
+{
+ cluster = RadosTest::s_cluster;
+ ASSERT_EQ(0, rados_ioctx_create(cluster, pool_name.c_str(), &ioctx));
+ nspace = get_temp_pool_name();
+ rados_ioctx_set_namespace(ioctx, nspace.c_str());
+ int req;
+ ASSERT_EQ(0, rados_ioctx_pool_requires_alignment2(ioctx, &req));
+ ASSERT_FALSE(req);
+}
+
+void RadosTest::TearDown()
+{
+ if (cleanup) {
+ cleanup_default_namespace(ioctx);
+ cleanup_namespace(ioctx, nspace);
+ }
+ rados_ioctx_destroy(ioctx);
+}
+
+void RadosTest::cleanup_default_namespace(rados_ioctx_t ioctx)
+{
+ // remove all objects from the default namespace to avoid polluting
+ // other tests
+ cleanup_namespace(ioctx, "");
+}
+
+void RadosTest::cleanup_namespace(rados_ioctx_t ioctx, std::string ns)
+{
+ rados_ioctx_snap_set_read(ioctx, LIBRADOS_SNAP_HEAD);
+ rados_ioctx_set_namespace(ioctx, ns.c_str());
+ rados_list_ctx_t list_ctx;
+
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &list_ctx));
+ auto sg = make_scope_guard([&] { rados_nobjects_list_close(list_ctx); });
+
+ int r;
+ const char *entry = NULL;
+ const char *key = NULL;
+ while ((r = rados_nobjects_list_next(list_ctx, &entry, &key, NULL)) != -ENOENT) {
+ ASSERT_EQ(0, r);
+ rados_ioctx_locator_set_key(ioctx, key);
+ ASSERT_EQ(0, rados_remove(ioctx, entry));
+ }
+}
+
+std::string RadosTestEC::pool_name;
+rados_t RadosTestEC::s_cluster = NULL;
+
+void RadosTestEC::SetUpTestCase()
+{
+ SKIP_IF_CRIMSON();
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_ec_pool(pool_name, &s_cluster));
+}
+
+void RadosTestEC::TearDownTestCase()
+{
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, destroy_one_ec_pool(pool_name, &s_cluster));
+}
+
+void RadosTestEC::SetUp()
+{
+ SKIP_IF_CRIMSON();
+ cluster = RadosTestEC::s_cluster;
+ ASSERT_EQ(0, rados_ioctx_create(cluster, pool_name.c_str(), &ioctx));
+ nspace = get_temp_pool_name();
+ rados_ioctx_set_namespace(ioctx, nspace.c_str());
+ int req;
+ ASSERT_EQ(0, rados_ioctx_pool_requires_alignment2(ioctx, &req));
+ ASSERT_TRUE(req);
+ ASSERT_EQ(0, rados_ioctx_pool_required_alignment2(ioctx, &alignment));
+ ASSERT_NE(0U, alignment);
+}
+
+void RadosTestEC::TearDown()
+{
+ SKIP_IF_CRIMSON();
+ if (cleanup) {
+ cleanup_default_namespace(ioctx);
+ cleanup_namespace(ioctx, nspace);
+ }
+ rados_ioctx_destroy(ioctx);
+}
+
diff --git a/src/test/librados/TestCase.h b/src/test/librados/TestCase.h
new file mode 100644
index 000000000..15fcfeb73
--- /dev/null
+++ b/src/test/librados/TestCase.h
@@ -0,0 +1,124 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_RADOS_TESTCASE_H
+#define CEPH_TEST_RADOS_TESTCASE_H
+
+#include "include/rados/librados.h"
+#include "gtest/gtest.h"
+
+#include <string>
+
+/**
+ * These test cases create a temporary pool that lives as long as the
+ * test case. We initially use the default namespace and assume
+ * test will whatever namespaces it wants. After each test all objects
+ * are removed.
+ *
+ * Since pool creation and deletion is slow, this allows many tests to
+ * run faster.
+ */
+class RadosTestNS : public ::testing::Test {
+public:
+ RadosTestNS(bool c=false) : cleanup(c) {}
+ ~RadosTestNS() override {}
+protected:
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+ static void cleanup_all_objects(rados_ioctx_t ioctx);
+ static rados_t s_cluster;
+ static std::string pool_name;
+
+ void SetUp() override;
+ void TearDown() override;
+ rados_t cluster = nullptr;
+ rados_ioctx_t ioctx = nullptr;
+ bool cleanup;
+};
+
+struct RadosTestNSCleanup : public RadosTestNS {
+ RadosTestNSCleanup() : RadosTestNS(true) {}
+};
+
+class RadosTestECNS : public RadosTestNS {
+public:
+ RadosTestECNS(bool c=false) : cleanup(c) {}
+ ~RadosTestECNS() override {}
+protected:
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+ static rados_t s_cluster;
+ static std::string pool_name;
+
+ void SetUp() override;
+ void TearDown() override;
+ rados_t cluster = nullptr;
+ rados_ioctx_t ioctx = nullptr;
+ uint64_t alignment = 0;
+ bool cleanup;
+};
+
+struct RadosTestECNSCleanup : public RadosTestECNS {
+ RadosTestECNSCleanup() : RadosTestECNS(true) {}
+};
+
+/**
+ * These test cases create a temporary pool that lives as long as the
+ * test case. Each test within a test case gets a new ioctx set to a
+ * unique namespace within the pool.
+ *
+ * Since pool creation and deletion is slow, this allows many tests to
+ * run faster.
+ */
+class RadosTest : public ::testing::Test {
+public:
+ RadosTest(bool c=false) : cleanup(c) {}
+ ~RadosTest() override {}
+protected:
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+ static void cleanup_default_namespace(rados_ioctx_t ioctx);
+ static void cleanup_namespace(rados_ioctx_t ioctx, std::string ns);
+ static rados_t s_cluster;
+ static std::string pool_name;
+
+ void SetUp() override;
+ void TearDown() override;
+ rados_t cluster = nullptr;
+ rados_ioctx_t ioctx = nullptr;
+ std::string nspace;
+ bool cleanup;
+};
+
+class RadosTestEC : public RadosTest {
+public:
+ RadosTestEC(bool c=false) : cleanup(c) {}
+ ~RadosTestEC() override {}
+protected:
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+ static rados_t s_cluster;
+ static std::string pool_name;
+
+ void SetUp() override;
+ void TearDown() override;
+ rados_t cluster = nullptr;
+ rados_ioctx_t ioctx = nullptr;
+ bool cleanup;
+ std::string nspace;
+ uint64_t alignment = 0;
+};
+
+/**
+ * Test case without creating a temporary pool in advance.
+ * This is necessary for scenarios such that we need to
+ * manually create a pool, start some long-runing tasks and
+ * then the related pool is suddenly gone.
+ */
+class RadosTestNP: public ::testing::Test {
+public:
+ RadosTestNP() {}
+ ~RadosTestNP() override {}
+};
+
+#endif
diff --git a/src/test/librados/aio.cc b/src/test/librados/aio.cc
new file mode 100644
index 000000000..68587fe87
--- /dev/null
+++ b/src/test/librados/aio.cc
@@ -0,0 +1,1724 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <string>
+#include <sstream>
+#include <utility>
+#include <boost/scoped_ptr.hpp>
+#include <fmt/format.h>
+
+#include "include/err.h"
+#include "include/rados/librados.h"
+#include "include/types.h"
+#include "include/stringify.h"
+#include "include/scope_guard.h"
+
+#include "common/errno.h"
+
+#include "gtest/gtest.h"
+
+#include "test.h"
+#include "crimson_utils.h"
+
+using std::ostringstream;
+
+class AioTestData
+{
+public:
+ AioTestData()
+ : m_cluster(NULL),
+ m_ioctx(NULL),
+ m_init(false)
+ {
+ }
+
+ ~AioTestData()
+ {
+ if (m_init) {
+ rados_ioctx_destroy(m_ioctx);
+ destroy_one_pool(m_pool_name, &m_cluster);
+ }
+ }
+
+ std::string init()
+ {
+ int ret;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ m_pool_name = get_temp_pool_name(pool_prefix);
+ std::string err = create_one_pool(m_pool_name, &m_cluster);
+ if (!err.empty()) {
+ ostringstream oss;
+ oss << "create_one_pool(" << m_pool_name << ") failed: error " << err;
+ return oss.str();
+ }
+ ret = rados_ioctx_create(m_cluster, m_pool_name.c_str(), &m_ioctx);
+ if (ret) {
+ destroy_one_pool(m_pool_name, &m_cluster);
+ ostringstream oss;
+ oss << "rados_ioctx_create failed: error " << ret;
+ return oss.str();
+ }
+ m_init = true;
+ return "";
+ }
+
+ rados_t m_cluster;
+ rados_ioctx_t m_ioctx;
+ std::string m_pool_name;
+ bool m_init;
+};
+
+TEST(LibRadosAio, TooBig) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(-E2BIG, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, UINT_MAX, 0));
+ ASSERT_EQ(-E2BIG, rados_aio_write_full(test_data.m_ioctx, "foo",
+ my_completion, buf, UINT_MAX));
+ ASSERT_EQ(-E2BIG, rados_aio_append(test_data.m_ioctx, "foo",
+ my_completion, buf, UINT_MAX));
+ rados_aio_release(my_completion);
+}
+
+TEST(LibRadosAio, SimpleWrite) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ auto sg = make_scope_guard([&] { rados_aio_release(my_completion); });
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+
+ rados_ioctx_set_namespace(test_data.m_ioctx, "nspace");
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ auto sg2 = make_scope_guard([&] { rados_aio_release(my_completion2); });
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion2, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+}
+
+TEST(LibRadosAio, WaitForSafe) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ rados_aio_release(my_completion);
+}
+
+TEST(LibRadosAio, RoundTrip) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[256];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ((int)sizeof(buf), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAio, RoundTrip2) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ((int)sizeof(buf), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAio, RoundTrip3) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+
+ rados_write_op_t op1 = rados_create_write_op();
+ rados_write_op_write(op1, buf, sizeof(buf), 0);
+ rados_write_op_set_alloc_hint2(op1, 0, 0, LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ASSERT_EQ(0, rados_aio_write_op_operate(op1, test_data.m_ioctx, my_completion,
+ "foo", NULL, 0));
+ rados_release_write_op(op1);
+
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ rados_aio_release(my_completion);
+
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+
+ rados_read_op_t op2 = rados_create_read_op();
+ rados_read_op_read(op2, 0, sizeof(buf2), buf2, NULL, NULL);
+ rados_read_op_set_flags(op2, LIBRADOS_OP_FLAG_FADVISE_NOCACHE |
+ LIBRADOS_OP_FLAG_FADVISE_RANDOM);
+ ceph_le32 init_value(-1);
+ ceph_le32 checksum[2];
+ rados_read_op_checksum(op2, LIBRADOS_CHECKSUM_TYPE_CRC32C,
+ reinterpret_cast<char *>(&init_value),
+ sizeof(init_value), 0, 0, 0,
+ reinterpret_cast<char *>(&checksum),
+ sizeof(checksum), NULL);
+ ASSERT_EQ(0, rados_aio_read_op_operate(op2, test_data.m_ioctx, my_completion2,
+ "foo", 0));
+ rados_release_read_op(op2);
+
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion2);
+
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(1U, checksum[0]);
+ ASSERT_EQ(bl.crc32c(-1), checksum[1]);
+}
+
+TEST(LibRadosAio, RoundTripAppend) {
+ AioTestData test_data;
+ rados_completion_t my_completion, my_completion2, my_completion3;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_append(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[128];
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_append(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ char buf3[sizeof(buf) + sizeof(buf2)];
+ memset(buf3, 0, sizeof(buf3));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion3, buf3, sizeof(buf3), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ((int)sizeof(buf3), rados_aio_get_return_value(my_completion3));
+ ASSERT_EQ(0, memcmp(buf3, buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(buf3 + sizeof(buf), buf2, sizeof(buf2)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+}
+
+TEST(LibRadosAio, RemoveTest) {
+ char buf[128];
+ char buf2[sizeof(buf)];
+ rados_completion_t my_completion;
+ AioTestData test_data;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(test_data.m_ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_aio_remove(test_data.m_ioctx, "foo", my_completion));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ memset(buf2, 0, sizeof(buf2));
+ ASSERT_EQ(-ENOENT, rados_read(test_data.m_ioctx, "foo", buf2, sizeof(buf2), 0));
+ rados_aio_release(my_completion);
+}
+
+TEST(LibRadosAio, XattrsRoundTrip) {
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ // append
+ AioTestData test_data;
+ ASSERT_EQ("", test_data.init());
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(test_data.m_ioctx, "foo", buf, sizeof(buf)));
+ // async getxattr
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ ASSERT_EQ(0, rados_aio_getxattr(test_data.m_ioctx, "foo", my_completion, attr1, buf, sizeof(buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(-ENODATA, rados_aio_get_return_value(my_completion));
+ rados_aio_release(my_completion);
+ // async setxattr
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_setxattr(test_data.m_ioctx, "foo", my_completion2, attr1, attr1_buf, sizeof(attr1_buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ rados_aio_release(my_completion2);
+ // async getxattr
+ rados_completion_t my_completion3;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_getxattr(test_data.m_ioctx, "foo", my_completion3, attr1, buf, sizeof(buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ((int)sizeof(attr1_buf), rados_aio_get_return_value(my_completion3));
+ rados_aio_release(my_completion3);
+ // check content of attribute
+ ASSERT_EQ(0, memcmp(attr1_buf, buf, sizeof(attr1_buf)));
+}
+
+TEST(LibRadosAio, RmXattr) {
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ // append
+ memset(buf, 0xaa, sizeof(buf));
+ AioTestData test_data;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_append(test_data.m_ioctx, "foo", buf, sizeof(buf)));
+ // async setxattr
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ ASSERT_EQ(0, rados_aio_setxattr(test_data.m_ioctx, "foo", my_completion, attr1, attr1_buf, sizeof(attr1_buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ rados_aio_release(my_completion);
+ // async rmxattr
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_rmxattr(test_data.m_ioctx, "foo", my_completion2, attr1));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ rados_aio_release(my_completion2);
+ // async getxattr after deletion
+ rados_completion_t my_completion3;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_getxattr(test_data.m_ioctx, "foo", my_completion3, attr1, buf, sizeof(buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ(-ENODATA, rados_aio_get_return_value(my_completion3));
+ rados_aio_release(my_completion3);
+ // Test rmxattr on a removed object
+ char buf2[128];
+ char attr2[] = "attr2";
+ char attr2_buf[] = "foo bar baz";
+ memset(buf2, 0xbb, sizeof(buf2));
+ ASSERT_EQ(0, rados_write(test_data.m_ioctx, "foo_rmxattr", buf2, sizeof(buf2), 0));
+ // asynx setxattr
+ rados_completion_t my_completion4;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion4));
+ ASSERT_EQ(0, rados_aio_setxattr(test_data.m_ioctx, "foo_rmxattr", my_completion4, attr2, attr2_buf, sizeof(attr2_buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion4));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion4));
+ rados_aio_release(my_completion4);
+ // remove object
+ ASSERT_EQ(0, rados_remove(test_data.m_ioctx, "foo_rmxattr"));
+ // async rmxattr on non existing object
+ rados_completion_t my_completion5;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion5));
+ ASSERT_EQ(0, rados_aio_rmxattr(test_data.m_ioctx, "foo_rmxattr", my_completion5, attr2));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion5));
+ }
+ ASSERT_EQ(-ENOENT, rados_aio_get_return_value(my_completion5));
+ rados_aio_release(my_completion5);
+}
+
+TEST(LibRadosAio, XattrIter) {
+ AioTestData test_data;
+ ASSERT_EQ("", test_data.init());
+ // Create an object with 2 attributes
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ char attr2[] = "attr2";
+ char attr2_buf[256];
+ for (size_t j = 0; j < sizeof(attr2_buf); ++j) {
+ attr2_buf[j] = j % 0xff;
+ }
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(test_data.m_ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_setxattr(test_data.m_ioctx, "foo", attr1, attr1_buf, sizeof(attr1_buf)));
+ ASSERT_EQ(0, rados_setxattr(test_data.m_ioctx, "foo", attr2, attr2_buf, sizeof(attr2_buf)));
+ // call async version of getxattrs and wait for completion
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2((void*)&test_data,
+ nullptr, &my_completion));
+ rados_xattrs_iter_t iter;
+ ASSERT_EQ(0, rados_aio_getxattrs(test_data.m_ioctx, "foo", my_completion, &iter));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ // loop over attributes
+ int num_seen = 0;
+ while (true) {
+ const char *name;
+ const char *val;
+ size_t len;
+ ASSERT_EQ(0, rados_getxattrs_next(iter, &name, &val, &len));
+ if (name == NULL) {
+ break;
+ }
+ ASSERT_LT(num_seen, 2);
+ if ((strcmp(name, attr1) == 0) && (val != NULL) && (memcmp(val, attr1_buf, len) == 0)) {
+ num_seen++;
+ continue;
+ }
+ else if ((strcmp(name, attr2) == 0) && (val != NULL) && (memcmp(val, attr2_buf, len) == 0)) {
+ num_seen++;
+ continue;
+ }
+ else {
+ ASSERT_EQ(0, 1);
+ }
+ }
+ rados_getxattrs_end(iter);
+}
+
+TEST(LibRadosAio, IsComplete) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+
+ // Busy-wait until the AIO completes.
+ // Normally we wouldn't do this, but we want to test rados_aio_is_complete.
+ while (true) {
+ int is_complete = rados_aio_is_complete(my_completion2);
+ if (is_complete)
+ break;
+ }
+ }
+ ASSERT_EQ((int)sizeof(buf), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAio, IsSafe) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+
+ // Busy-wait until the AIO completes.
+ // Normally we wouldn't do this, but we want to test rados_aio_is_safe.
+ while (true) {
+ int is_safe = rados_aio_is_safe(my_completion);
+ if (is_safe)
+ break;
+ }
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ((int)sizeof(buf), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAio, ReturnValue) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "nonexistent",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(-ENOENT, rados_aio_get_return_value(my_completion));
+ rados_aio_release(my_completion);
+}
+
+TEST(LibRadosAio, Flush) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xee, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_aio_flush(test_data.m_ioctx));
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ((int)sizeof(buf2), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAio, FlushAsync) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ rados_completion_t flush_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &flush_completion));
+ char buf[128];
+ memset(buf, 0xee, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_aio_flush_async(test_data.m_ioctx, flush_completion));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(flush_completion));
+ }
+ ASSERT_EQ(1, rados_aio_is_complete(my_completion));
+ ASSERT_EQ(1, rados_aio_is_complete(flush_completion));
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ((int)sizeof(buf2), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(flush_completion);
+}
+
+TEST(LibRadosAio, RoundTripWriteFull) {
+ AioTestData test_data;
+ rados_completion_t my_completion, my_completion2, my_completion3;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[64];
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_write_full(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ char buf3[sizeof(buf) + sizeof(buf2)];
+ memset(buf3, 0, sizeof(buf3));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion3, buf3, sizeof(buf3), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ((int)sizeof(buf2), rados_aio_get_return_value(my_completion3));
+ ASSERT_EQ(0, memcmp(buf3, buf2, sizeof(buf2)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+}
+
+TEST(LibRadosAio, RoundTripWriteSame) {
+ AioTestData test_data;
+ rados_completion_t my_completion, my_completion2, my_completion3;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char full[128];
+ memset(full, 0xcc, sizeof(full));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, full, sizeof(full), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ /* write the same buf four times */
+ char buf[32];
+ size_t ws_write_len = sizeof(full);
+ memset(buf, 0xdd, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_writesame(test_data.m_ioctx, "foo",
+ my_completion2, buf, sizeof(buf),
+ ws_write_len, 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion3, full, sizeof(full), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ((int)sizeof(full), rados_aio_get_return_value(my_completion3));
+ for (char *cmp = full; cmp < full + sizeof(full); cmp += sizeof(buf)) {
+ ASSERT_EQ(0, memcmp(cmp, buf, sizeof(buf)));
+ }
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+}
+
+TEST(LibRadosAio, SimpleStat) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ uint64_t psize;
+ time_t pmtime;
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_stat(test_data.m_ioctx, "foo",
+ my_completion2, &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(sizeof(buf), psize);
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAio, OperateMtime)
+{
+ AioTestData test_data;
+ ASSERT_EQ("", test_data.init());
+
+ time_t set_mtime = 1457129052;
+ {
+ rados_write_op_t op = rados_create_write_op();
+ rados_write_op_create(op, LIBRADOS_CREATE_IDEMPOTENT, nullptr);
+ rados_completion_t completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &completion));
+ ASSERT_EQ(0, rados_aio_write_op_operate(op, test_data.m_ioctx, completion,
+ "foo", &set_mtime, 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(completion));
+ rados_aio_release(completion);
+ rados_release_write_op(op);
+ }
+ {
+ uint64_t size;
+ timespec mtime;
+ ASSERT_EQ(0, rados_stat2(test_data.m_ioctx, "foo", &size, &mtime));
+ EXPECT_EQ(0, size);
+ EXPECT_EQ(set_mtime, mtime.tv_sec);
+ EXPECT_EQ(0, mtime.tv_nsec);
+ }
+}
+
+TEST(LibRadosAio, Operate2Mtime)
+{
+ AioTestData test_data;
+ ASSERT_EQ("", test_data.init());
+
+ timespec set_mtime{1457129052, 123456789};
+ {
+ rados_write_op_t op = rados_create_write_op();
+ rados_write_op_create(op, LIBRADOS_CREATE_IDEMPOTENT, nullptr);
+ rados_completion_t completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &completion));
+ ASSERT_EQ(0, rados_aio_write_op_operate2(op, test_data.m_ioctx, completion,
+ "foo", &set_mtime, 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(completion));
+ rados_aio_release(completion);
+ rados_release_write_op(op);
+ }
+ {
+ uint64_t size;
+ timespec mtime;
+ ASSERT_EQ(0, rados_stat2(test_data.m_ioctx, "foo", &size, &mtime));
+ EXPECT_EQ(0, size);
+ EXPECT_EQ(set_mtime.tv_sec, mtime.tv_sec);
+ EXPECT_EQ(set_mtime.tv_nsec, mtime.tv_nsec);
+ }
+}
+
+TEST(LibRadosAio, SimpleStatNS) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ rados_ioctx_set_namespace(test_data.m_ioctx, "nspace");
+ char buf2[64];
+ memset(buf2, 0xbb, sizeof(buf2));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ uint64_t psize;
+ time_t pmtime;
+ rados_completion_t my_completion2;
+ rados_ioctx_set_namespace(test_data.m_ioctx, "");
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_stat(test_data.m_ioctx, "foo",
+ my_completion2, &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(sizeof(buf), psize);
+
+ rados_ioctx_set_namespace(test_data.m_ioctx, "nspace");
+ rados_completion_t my_completion3;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_stat(test_data.m_ioctx, "foo",
+ my_completion3, &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion3));
+ ASSERT_EQ(sizeof(buf2), psize);
+
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+}
+
+TEST(LibRadosAio, StatRemove) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ uint64_t psize;
+ time_t pmtime;
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_stat(test_data.m_ioctx, "foo",
+ my_completion2, &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(sizeof(buf), psize);
+ rados_completion_t my_completion3;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_remove(test_data.m_ioctx, "foo", my_completion3));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion3));
+ uint64_t psize2;
+ time_t pmtime2;
+ rados_completion_t my_completion4;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion4));
+ ASSERT_EQ(0, rados_aio_stat(test_data.m_ioctx, "foo",
+ my_completion4, &psize2, &pmtime2));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion4));
+ }
+ ASSERT_EQ(-ENOENT, rados_aio_get_return_value(my_completion4));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+ rados_aio_release(my_completion4);
+}
+
+TEST(LibRadosAio, ExecuteClass) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ }
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ char out[128];
+ ASSERT_EQ(0, rados_aio_exec(test_data.m_ioctx, "foo", my_completion2,
+ "hello", "say_hello", NULL, 0, out, sizeof(out)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(13, rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, strncmp("Hello, world!", out, 13));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+using std::string;
+using std::map;
+using std::set;
+
+TEST(LibRadosAio, MultiWrite) {
+ AioTestData test_data;
+ rados_completion_t my_completion, my_completion2, my_completion3;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+
+ char buf2[64];
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), sizeof(buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+
+ char buf3[(sizeof(buf) + sizeof(buf2)) * 3];
+ memset(buf3, 0, sizeof(buf3));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion3, buf3, sizeof(buf3), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ((int)(sizeof(buf) + sizeof(buf2)), rados_aio_get_return_value(my_completion3));
+ ASSERT_EQ(0, memcmp(buf3, buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(buf3 + sizeof(buf), buf2, sizeof(buf2)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+}
+
+TEST(LibRadosAio, AioUnlock) {
+ AioTestData test_data;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_lock_exclusive(test_data.m_ioctx, "foo", "TestLock", "Cookie", "", NULL, 0));
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ ASSERT_EQ(0, rados_aio_unlock(test_data.m_ioctx, "foo", "TestLock", "Cookie", my_completion));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ ASSERT_EQ(0, rados_lock_exclusive(test_data.m_ioctx, "foo", "TestLock", "Cookie", "", NULL, 0));
+}
+
+// EC test cases
+class AioTestDataEC
+{
+public:
+ AioTestDataEC()
+ : m_cluster(NULL),
+ m_ioctx(NULL),
+ m_init(false)
+ {
+ }
+
+ ~AioTestDataEC()
+ {
+ if (m_init) {
+ rados_ioctx_destroy(m_ioctx);
+ destroy_one_ec_pool(m_pool_name, &m_cluster);
+ }
+ }
+
+ std::string init()
+ {
+ int ret;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ m_pool_name = get_temp_pool_name(pool_prefix);
+ std::string err = create_one_ec_pool(m_pool_name, &m_cluster);
+ if (!err.empty()) {
+ ostringstream oss;
+ oss << "create_one_ec_pool(" << m_pool_name << ") failed: error " << err;
+ return oss.str();
+ }
+ ret = rados_ioctx_create(m_cluster, m_pool_name.c_str(), &m_ioctx);
+ if (ret) {
+ destroy_one_ec_pool(m_pool_name, &m_cluster);
+ ostringstream oss;
+ oss << "rados_ioctx_create failed: error " << ret;
+ return oss.str();
+ }
+ m_init = true;
+ return "";
+ }
+
+ rados_t m_cluster;
+ rados_ioctx_t m_ioctx;
+ std::string m_pool_name;
+ bool m_init;
+};
+
+TEST(LibRadosAioEC, SimpleWrite) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ auto sg = make_scope_guard([&] { rados_aio_release(my_completion); });
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+
+ rados_ioctx_set_namespace(test_data.m_ioctx, "nspace");
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ auto sg2 = make_scope_guard([&] { rados_aio_release(my_completion2); });
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion2, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+}
+
+TEST(LibRadosAioEC, WaitForComplete) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ rados_aio_release(my_completion);
+}
+
+TEST(LibRadosAioEC, RoundTrip) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[256];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ((int)sizeof(buf), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAioEC, RoundTrip2) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ((int)sizeof(buf), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAioEC, RoundTripAppend) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion, my_completion2, my_completion3, my_completion4;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ int req;
+ ASSERT_EQ(0, rados_ioctx_pool_requires_alignment2(test_data.m_ioctx, &req));
+ ASSERT_NE(0, req);
+ uint64_t alignment;
+ ASSERT_EQ(0, rados_ioctx_pool_required_alignment2(test_data.m_ioctx, &alignment));
+ ASSERT_NE(0U, alignment);
+
+ int bsize = alignment;
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ ASSERT_EQ(0, rados_aio_append(test_data.m_ioctx, "foo",
+ my_completion, buf, bsize));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+
+ int hbsize = bsize / 2;
+ char *buf2 = (char *)new char[hbsize];
+ memset(buf2, 0xdd, hbsize);
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_append(test_data.m_ioctx, "foo",
+ my_completion2, buf2, hbsize));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_append(test_data.m_ioctx, "foo",
+ my_completion3, buf2, hbsize));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ EXPECT_EQ(-EOPNOTSUPP, rados_aio_get_return_value(my_completion3));
+
+ int tbsize = bsize + hbsize;
+ char *buf3 = (char *)new char[tbsize];
+ memset(buf3, 0, tbsize);
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion4));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion4, buf3, bsize * 3, 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion4));
+ }
+ ASSERT_EQ(tbsize, rados_aio_get_return_value(my_completion4));
+ ASSERT_EQ(0, memcmp(buf3, buf, bsize));
+ ASSERT_EQ(0, memcmp(buf3 + bsize, buf2, hbsize));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+ rados_aio_release(my_completion4);
+ delete[] buf;
+ delete[] buf2;
+ delete[] buf3;
+}
+
+TEST(LibRadosAioEC, IsComplete) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+
+ // Busy-wait until the AIO completes.
+ // Normally we wouldn't do this, but we want to test rados_aio_is_complete.
+ while (true) {
+ int is_complete = rados_aio_is_complete(my_completion2);
+ if (is_complete)
+ break;
+ }
+ }
+ ASSERT_EQ((int)sizeof(buf), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAioEC, IsSafe) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+
+ // Busy-wait until the AIO completes.
+ // Normally we wouldn't do this, but we want to test rados_aio_is_safe.
+ while (true) {
+ int is_safe = rados_aio_is_safe(my_completion);
+ if (is_safe)
+ break;
+ }
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ((int)sizeof(buf), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAioEC, ReturnValue) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "nonexistent",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(-ENOENT, rados_aio_get_return_value(my_completion));
+ rados_aio_release(my_completion);
+}
+
+TEST(LibRadosAioEC, Flush) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xee, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_aio_flush(test_data.m_ioctx));
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ((int)sizeof(buf2), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAioEC, FlushAsync) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ rados_completion_t flush_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &flush_completion));
+ char buf[128];
+ memset(buf, 0xee, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_aio_flush_async(test_data.m_ioctx, flush_completion));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(flush_completion));
+ }
+ ASSERT_EQ(1, rados_aio_is_complete(my_completion));
+ ASSERT_EQ(1, rados_aio_is_complete(flush_completion));
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ((int)sizeof(buf2), rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(flush_completion);
+}
+
+TEST(LibRadosAioEC, RoundTripWriteFull) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion, my_completion2, my_completion3;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ char buf2[64];
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_write_full(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ char buf3[sizeof(buf) + sizeof(buf2)];
+ memset(buf3, 0, sizeof(buf3));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion3, buf3, sizeof(buf3), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ((int)sizeof(buf2), rados_aio_get_return_value(my_completion3));
+ ASSERT_EQ(0, memcmp(buf3, buf2, sizeof(buf2)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+}
+
+TEST(LibRadosAioEC, SimpleStat) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ uint64_t psize;
+ time_t pmtime;
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_stat(test_data.m_ioctx, "foo",
+ my_completion2, &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(sizeof(buf), psize);
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+
+TEST(LibRadosAioEC, SimpleStatNS) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ rados_ioctx_set_namespace(test_data.m_ioctx, "nspace");
+ char buf2[64];
+ memset(buf2, 0xbb, sizeof(buf2));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ uint64_t psize;
+ time_t pmtime;
+ rados_completion_t my_completion2;
+ rados_ioctx_set_namespace(test_data.m_ioctx, "");
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_stat(test_data.m_ioctx, "foo",
+ my_completion2, &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(sizeof(buf), psize);
+
+ rados_ioctx_set_namespace(test_data.m_ioctx, "nspace");
+ rados_completion_t my_completion3;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_stat(test_data.m_ioctx, "foo",
+ my_completion3, &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion3));
+ ASSERT_EQ(sizeof(buf2), psize);
+
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+}
+
+TEST(LibRadosAioEC, StatRemove) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ uint64_t psize;
+ time_t pmtime;
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_stat(test_data.m_ioctx, "foo",
+ my_completion2, &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(sizeof(buf), psize);
+ rados_completion_t my_completion3;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_remove(test_data.m_ioctx, "foo", my_completion3));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion3));
+ uint64_t psize2;
+ time_t pmtime2;
+ rados_completion_t my_completion4;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion4));
+ ASSERT_EQ(0, rados_aio_stat(test_data.m_ioctx, "foo",
+ my_completion4, &psize2, &pmtime2));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion4));
+ }
+ ASSERT_EQ(-ENOENT, rados_aio_get_return_value(my_completion4));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+ rados_aio_release(my_completion4);
+}
+
+TEST(LibRadosAioEC, ExecuteClass) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ char out[128];
+ ASSERT_EQ(0, rados_aio_exec(test_data.m_ioctx, "foo", my_completion2,
+ "hello", "say_hello", NULL, 0, out, sizeof(out)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(13, rados_aio_get_return_value(my_completion2));
+ ASSERT_EQ(0, strncmp("Hello, world!", out, 13));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST(LibRadosAioEC, MultiWrite) {
+ SKIP_IF_CRIMSON();
+ AioTestDataEC test_data;
+ rados_completion_t my_completion, my_completion2, my_completion3;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+
+ char buf2[64];
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion2));
+ ASSERT_EQ(0, rados_aio_write(test_data.m_ioctx, "foo",
+ my_completion2, buf2, sizeof(buf2), sizeof(buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion2));
+ }
+ ASSERT_EQ(-EOPNOTSUPP, rados_aio_get_return_value(my_completion2));
+
+ char buf3[(sizeof(buf) + sizeof(buf2)) * 3];
+ memset(buf3, 0, sizeof(buf3));
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr,
+ nullptr, &my_completion3));
+ ASSERT_EQ(0, rados_aio_read(test_data.m_ioctx, "foo",
+ my_completion3, buf3, sizeof(buf3), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion3));
+ }
+ ASSERT_EQ((int)sizeof(buf), rados_aio_get_return_value(my_completion3));
+ ASSERT_EQ(0, memcmp(buf3, buf, sizeof(buf)));
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+}
diff --git a/src/test/librados/aio_cxx.cc b/src/test/librados/aio_cxx.cc
new file mode 100644
index 000000000..5647bd9c0
--- /dev/null
+++ b/src/test/librados/aio_cxx.cc
@@ -0,0 +1,2467 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <boost/scoped_ptr.hpp>
+#include <fmt/format.h>
+
+#include "gtest/gtest.h"
+
+#include "common/errno.h"
+#include "include/err.h"
+#include "include/rados/librados.hpp"
+#include "include/types.h"
+#include "include/stringify.h"
+#include "include/scope_guard.h"
+#include "common/ceph_mutex.h"
+#include <fmt/format.h>
+
+#include "test_cxx.h"
+#include "crimson_utils.h"
+
+using namespace std;
+using namespace librados;
+
+class AioTestDataPP
+{
+public:
+ AioTestDataPP()
+ : m_init(false),
+ m_oid("foo")
+ {
+ }
+
+ ~AioTestDataPP()
+ {
+ if (m_init) {
+ m_ioctx.close();
+ destroy_one_pool_pp(m_pool_name, m_cluster);
+ }
+ }
+
+ std::string init()
+ {
+ return init({});
+ }
+
+ std::string init(const std::map<std::string, std::string> &config)
+ {
+ int ret;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ m_pool_name = get_temp_pool_name(pool_prefix);
+ std::string err = create_one_pool_pp(m_pool_name, m_cluster, config);
+ if (!err.empty()) {
+ ostringstream oss;
+ oss << "create_one_pool(" << m_pool_name << ") failed: error " << err;
+ return oss.str();
+ }
+ ret = m_cluster.ioctx_create(m_pool_name.c_str(), m_ioctx);
+ if (ret) {
+ destroy_one_pool_pp(m_pool_name, m_cluster);
+ ostringstream oss;
+ oss << "rados_ioctx_create failed: error " << ret;
+ return oss.str();
+ }
+ m_oid = fmt::format("oid_{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ m_init = true;
+ return "";
+ }
+
+ Rados m_cluster;
+ IoCtx m_ioctx;
+ std::string m_pool_name;
+ bool m_init;
+ std::string m_oid;
+};
+
+TEST(LibRadosAio, TooBigPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+
+ bufferlist bl;
+ auto aio_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(-E2BIG, test_data.m_ioctx.aio_write(test_data.m_oid, aio_completion.get(), bl, UINT_MAX, 0));
+ ASSERT_EQ(-E2BIG, test_data.m_ioctx.aio_append(test_data.m_oid, aio_completion.get(), bl, UINT_MAX));
+ // ioctx.aio_write_full no way to overflow bl.length()
+}
+
+TEST(LibRadosAio, PoolQuotaPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ string p = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ(0, test_data.m_cluster.pool_create(p.c_str()));
+ IoCtx ioctx;
+ ASSERT_EQ(0, test_data.m_cluster.ioctx_create(p.c_str(), ioctx));
+ ioctx.application_enable("rados", true);
+
+ bufferlist inbl;
+ ASSERT_EQ(0, test_data.m_cluster.mon_command(
+ "{\"prefix\": \"osd pool set-quota\", \"pool\": \"" + p +
+ "\", \"field\": \"max_bytes\", \"val\": \"4096\"}",
+ inbl, NULL, NULL));
+
+ bufferlist bl;
+ bufferptr z(4096);
+ bl.append(z);
+ int n;
+ for (n = 0; n < 1024; ++n) {
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ auto completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, ioctx.aio_operate(test_data.m_oid + stringify(n),
+ completion.get(), &op,
+ librados::OPERATION_FULL_TRY));
+ completion->wait_for_complete();
+ int r = completion->get_return_value();
+ if (r == -EDQUOT)
+ break;
+ ASSERT_EQ(0, r);
+ sleep(1);
+ }
+ ASSERT_LT(n, 1024);
+
+ // make sure we have latest map that marked the pool full
+ test_data.m_cluster.wait_for_latest_osdmap();
+
+ // make sure we block without FULL_TRY
+ {
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ auto completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, ioctx.aio_operate("bar", completion.get(), &op, 0));
+ sleep(5);
+ ASSERT_FALSE(completion->is_complete());
+ }
+
+ ioctx.close();
+ ASSERT_EQ(0, test_data.m_cluster.pool_delete(p.c_str()));
+}
+
+TEST(LibRadosAio, SimpleWritePP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ }
+
+ {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ test_data.m_ioctx.set_namespace("nspace");
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ }
+}
+
+TEST(LibRadosAio, WaitForSafePP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ ASSERT_EQ(0, my_completion->get_return_value());
+}
+
+TEST(LibRadosAio, RoundTripPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+TEST(LibRadosAio, RoundTripPP2) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+//using ObjectWriteOperation/ObjectReadOperation with iohint
+TEST(LibRadosAio, RoundTripPP3)
+{
+ Rados cluster;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ std::string pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+ IoCtx ioctx;
+ cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+ auto my_completion1 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ObjectWriteOperation op;
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ op.write(0, bl);
+ op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ioctx.aio_operate("test_obj", my_completion1.get(), &op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion1->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion1->get_return_value());
+
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ bl.clear();
+ ObjectReadOperation op1;
+ op1.read(0, sizeof(buf), &bl, NULL);
+ op1.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED|LIBRADOS_OP_FLAG_FADVISE_RANDOM);
+ bufferlist init_value_bl;
+ encode(static_cast<int32_t>(-1), init_value_bl);
+ bufferlist csum_bl;
+ op1.checksum(LIBRADOS_CHECKSUM_TYPE_CRC32C, init_value_bl,
+ 0, 0, 0, &csum_bl, nullptr);
+ ioctx.aio_operate("test_obj", my_completion2.get(), &op1, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(0, memcmp(buf, bl.c_str(), sizeof(buf)));
+
+ ASSERT_EQ(8U, csum_bl.length());
+ auto csum_bl_it = csum_bl.cbegin();
+ uint32_t csum_count;
+ uint32_t csum;
+ decode(csum_count, csum_bl_it);
+ ASSERT_EQ(1U, csum_count);
+ decode(csum, csum_bl_it);
+ ASSERT_EQ(bl.crc32c(-1), csum);
+ ioctx.remove("test_obj");
+ destroy_one_pool_pp(pool_name, cluster);
+}
+
+TEST(LibRadosAio, RoundTripSparseReadPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ std::map<uint64_t, uint64_t> extents;
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_sparse_read(test_data.m_oid, my_completion2.get(),
+ &extents, &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ assert_eq_sparse(bl1, extents, bl2);
+}
+
+TEST(LibRadosAioPP, ReadIntoBufferlist) {
+
+ // here we test reading into a non-empty bufferlist referencing existing
+ // buffers
+
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+
+ bufferlist bl2;
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xbb, sizeof(buf2));
+ bl2.append(buffer::create_static(sizeof(buf2), buf2));
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+}
+
+TEST(LibRadosAioPP, XattrsRoundTripPP) {
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, test_data.m_ioctx.append(test_data.m_oid, bl1, sizeof(buf)));
+ bufferlist bl2;
+ // async getxattr
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_getxattr(test_data.m_oid, my_completion.get(), attr1, bl2));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(-ENODATA, my_completion->get_return_value());
+ // append
+ bufferlist bl3;
+ bl3.append(attr1_buf, sizeof(attr1_buf));
+ // async setxattr
+ AioTestDataPP test_data2;
+ ASSERT_EQ("", test_data2.init());
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_setxattr(test_data.m_oid, my_completion2.get(), attr1, bl3));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ // async getxattr
+ bufferlist bl4;
+ AioTestDataPP test_data3;
+ ASSERT_EQ("", test_data3.init());
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_getxattr(test_data.m_oid, my_completion3.get(), attr1, bl4));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(attr1_buf), my_completion3->get_return_value());
+ // check content of attribute
+ ASSERT_EQ(0, memcmp(bl4.c_str(), attr1_buf, sizeof(attr1_buf)));
+}
+
+TEST(LibRadosAioPP, RmXattrPP) {
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, test_data.m_ioctx.append(test_data.m_oid, bl1, sizeof(buf)));
+ // async setxattr
+ bufferlist bl2;
+ bl2.append(attr1_buf, sizeof(attr1_buf));
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_setxattr(test_data.m_oid, my_completion.get(), attr1, bl2));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ // async rmxattr
+ AioTestDataPP test_data2;
+ ASSERT_EQ("", test_data2.init());
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_rmxattr(test_data.m_oid, my_completion2.get(), attr1));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ // async getxattr
+ AioTestDataPP test_data3;
+ ASSERT_EQ("", test_data3.init());
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ bufferlist bl3;
+ ASSERT_EQ(0, test_data.m_ioctx.aio_getxattr(test_data.m_oid, my_completion3.get(), attr1, bl3));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ ASSERT_EQ(-ENODATA, my_completion3->get_return_value());
+ // Test rmxattr on a removed object
+ char buf2[128];
+ char attr2[] = "attr2";
+ char attr2_buf[] = "foo bar baz";
+ memset(buf2, 0xbb, sizeof(buf2));
+ bufferlist bl21;
+ bl21.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.write("foo_rmxattr", bl21, sizeof(buf2), 0));
+ bufferlist bl22;
+ bl22.append(attr2_buf, sizeof(attr2_buf));
+ // async setxattr
+ AioTestDataPP test_data4;
+ ASSERT_EQ("", test_data4.init());
+ auto my_completion4 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_setxattr("foo_rmxattr", my_completion4.get(), attr2, bl22));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion4->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion4->get_return_value());
+ // remove object
+ ASSERT_EQ(0, test_data.m_ioctx.remove("foo_rmxattr"));
+ // async rmxattr on non existing object
+ AioTestDataPP test_data5;
+ ASSERT_EQ("", test_data5.init());
+ auto my_completion5 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_rmxattr("foo_rmxattr", my_completion5.get(), attr2));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion5->wait_for_complete());
+ }
+ ASSERT_EQ(-ENOENT, my_completion5->get_return_value());
+}
+
+TEST(LibRadosIoPP, XattrListPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ // create an object with 2 attributes
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ char attr2[] = "attr2";
+ char attr2_buf[256];
+ for (size_t j = 0; j < sizeof(attr2_buf); ++j) {
+ attr2_buf[j] = j % 0xff;
+ }
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.append(test_data.m_oid, bl1, sizeof(buf)));
+ bufferlist bl2;
+ bl2.append(attr1_buf, sizeof(attr1_buf));
+ ASSERT_EQ(0, test_data.m_ioctx.setxattr(test_data.m_oid, attr1, bl2));
+ bufferlist bl3;
+ bl3.append(attr2_buf, sizeof(attr2_buf));
+ ASSERT_EQ(0, test_data.m_ioctx.setxattr(test_data.m_oid, attr2, bl3));
+ // call async version of getxattrs
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ std::map<std::string, bufferlist> attrset;
+ ASSERT_EQ(0, test_data.m_ioctx.aio_getxattrs(test_data.m_oid, my_completion.get(), attrset));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ for (std::map<std::string, bufferlist>::iterator i = attrset.begin();
+ i != attrset.end(); ++i) {
+ if (i->first == string(attr1)) {
+ ASSERT_EQ(0, memcmp(i->second.c_str(), attr1_buf, sizeof(attr1_buf)));
+ }
+ else if (i->first == string(attr2)) {
+ ASSERT_EQ(0, memcmp(i->second.c_str(), attr2_buf, sizeof(attr2_buf)));
+ }
+ else {
+ ASSERT_EQ(0, 1);
+ }
+ }
+}
+
+TEST(LibRadosAio, IsCompletePP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+
+ // Busy-wait until the AIO completes.
+ // Normally we wouldn't do this, but we want to test is_complete.
+ while (true) {
+ int is_complete = my_completion2->is_complete();
+ if (is_complete)
+ break;
+ }
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+TEST(LibRadosAio, IsSafePP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+
+ // Busy-wait until the AIO completes.
+ // Normally we wouldn't do this, but we want to test rados_aio_is_safe.
+ while (true) {
+ int is_complete = my_completion->is_complete();
+ if (is_complete)
+ break;
+ }
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ bufferlist bl2;
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+TEST(LibRadosAio, ReturnValuePP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ bufferlist bl1;
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read("nonexistent", my_completion.get(),
+ &bl1, 128, 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(-ENOENT, my_completion->get_return_value());
+}
+
+TEST(LibRadosAio, FlushPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xee, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_flush());
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+TEST(LibRadosAio, FlushAsyncPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ auto flush_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xee, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_flush_async(flush_completion.get()));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, flush_completion->wait_for_complete());
+ }
+ ASSERT_EQ(1, my_completion->is_complete());
+ ASSERT_EQ(1, flush_completion->is_complete());
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+TEST(LibRadosAio, RoundTripWriteFullPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ char buf2[64];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write_full(test_data.m_oid, my_completion2.get(), bl2));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ bufferlist bl3;
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion3);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion3.get(),
+ &bl3, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf2), my_completion3->get_return_value());
+ ASSERT_EQ(sizeof(buf2), bl3.length());
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf2, sizeof(buf2)));
+}
+
+//using ObjectWriteOperation/ObjectReadOperation with iohint
+TEST(LibRadosAio, RoundTripWriteFullPP2)
+{
+ Rados cluster;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ std::string pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+ IoCtx ioctx;
+ cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+ auto my_completion1 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ObjectWriteOperation op;
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf);
+
+ op.write_full(bl);
+ op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ioctx.aio_operate("test_obj", my_completion1.get(), &op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion1->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion1->get_return_value());
+
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ bl.clear();
+ ObjectReadOperation op1;
+ op1.read(0, sizeof(buf), &bl, NULL);
+ op1.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED|LIBRADOS_OP_FLAG_FADVISE_RANDOM);
+ ioctx.aio_operate("test_obj", my_completion2.get(), &op1, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(0, memcmp(buf, bl.c_str(), sizeof(buf)));
+
+ ioctx.remove("test_obj");
+ destroy_one_pool_pp(pool_name, cluster);
+}
+
+TEST(LibRadosAio, RoundTripWriteSamePP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char full[128];
+ memset(full, 0xcc, sizeof(full));
+ bufferlist bl1;
+ bl1.append(full, sizeof(full));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(full), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ /* write the same buf four times */
+ char buf[32];
+ size_t ws_write_len = sizeof(full);
+ memset(buf, 0xdd, sizeof(buf));
+ bufferlist bl2;
+ bl2.append(buf, sizeof(buf));
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_writesame(test_data.m_oid, my_completion2.get(), bl2,
+ ws_write_len, 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ bufferlist bl3;
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion3);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion3.get(),
+ &bl3, sizeof(full), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(full), my_completion3->get_return_value());
+ ASSERT_EQ(sizeof(full), bl3.length());
+ for (char *cmp = bl3.c_str(); cmp < bl3.c_str() + bl3.length();
+ cmp += sizeof(buf)) {
+ ASSERT_EQ(0, memcmp(cmp, buf, sizeof(buf)));
+ }
+}
+
+TEST(LibRadosAio, RoundTripWriteSamePP2)
+{
+ Rados cluster;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ std::string pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+ IoCtx ioctx;
+ cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+ auto wr_cmpl = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ObjectWriteOperation op;
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ op.writesame(0, sizeof(buf) * 4, bl);
+ op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ioctx.aio_operate("test_obj", wr_cmpl.get(), &op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, wr_cmpl->wait_for_complete());
+ }
+ EXPECT_EQ(0, wr_cmpl->get_return_value());
+
+ boost::scoped_ptr<AioCompletion>
+ rd_cmpl(cluster.aio_create_completion(0, 0));
+ char *cmp;
+ char full[sizeof(buf) * 4];
+ memset(full, 0, sizeof(full));
+ bufferlist fl;
+ fl.append(full, sizeof(full));
+ ObjectReadOperation op1;
+ op1.read(0, sizeof(full), &fl, NULL);
+ op1.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ioctx.aio_operate("test_obj", rd_cmpl.get(), &op1, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rd_cmpl->wait_for_complete());
+ }
+ EXPECT_EQ(0, rd_cmpl->get_return_value());
+ for (cmp = fl.c_str(); cmp < fl.c_str() + fl.length(); cmp += sizeof(buf)) {
+ ASSERT_EQ(0, memcmp(cmp, buf, sizeof(buf)));
+ }
+
+ ioctx.remove("test_obj");
+ destroy_one_pool_pp(pool_name, cluster);
+}
+
+TEST(LibRadosAio, SimpleStatPPNS) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ uint64_t psize;
+ time_t pmtime;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_stat(test_data.m_oid, my_completion2.get(),
+ &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), psize);
+}
+
+TEST(LibRadosAio, SimpleStatPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ uint64_t psize;
+ time_t pmtime;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_stat(test_data.m_oid, my_completion2.get(),
+ &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), psize);
+}
+
+TEST(LibRadosAio, OperateMtime)
+{
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+
+ time_t set_mtime = 1457129052;
+ {
+ auto c = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ librados::ObjectWriteOperation op;
+ op.mtime(&set_mtime);
+ op.create(false);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_operate(test_data.m_oid, c.get(), &op));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, c->wait_for_complete());
+ }
+ ASSERT_EQ(0, c->get_return_value());
+ }
+ {
+ uint64_t size;
+ timespec mtime;
+ ASSERT_EQ(0, test_data.m_ioctx.stat2(test_data.m_oid, &size, &mtime));
+ EXPECT_EQ(0, size);
+ EXPECT_EQ(set_mtime, mtime.tv_sec);
+ EXPECT_EQ(0, mtime.tv_nsec);
+ }
+}
+
+TEST(LibRadosAio, OperateMtime2)
+{
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+
+ timespec set_mtime{1457129052, 123456789};
+ {
+ auto c = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ librados::ObjectWriteOperation op;
+ op.mtime2(&set_mtime);
+ op.create(false);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_operate(test_data.m_oid, c.get(), &op));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, c->wait_for_complete());
+ }
+ ASSERT_EQ(0, c->get_return_value());
+ }
+ {
+ uint64_t size;
+ timespec mtime;
+ ASSERT_EQ(0, test_data.m_ioctx.stat2(test_data.m_oid, &size, &mtime));
+ EXPECT_EQ(0, size);
+ EXPECT_EQ(set_mtime.tv_sec, mtime.tv_sec);
+ EXPECT_EQ(set_mtime.tv_nsec, mtime.tv_nsec);
+ }
+}
+
+TEST(LibRadosAio, StatRemovePP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ uint64_t psize;
+ time_t pmtime;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_stat(test_data.m_oid, my_completion2.get(),
+ &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), psize);
+ uint64_t psize2;
+ time_t pmtime2;
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion3);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_remove(test_data.m_oid, my_completion3.get()));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion3->get_return_value());
+
+ auto my_completion4 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion4);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_stat(test_data.m_oid, my_completion4.get(),
+ &psize2, &pmtime2));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion4->wait_for_complete());
+ }
+ ASSERT_EQ(-ENOENT, my_completion4->get_return_value());
+}
+
+TEST(LibRadosAio, ExecuteClassPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ bufferlist in, out;
+ ASSERT_EQ(0, test_data.m_ioctx.aio_exec(test_data.m_oid, my_completion2.get(),
+ "hello", "say_hello", in, &out));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(std::string("Hello, world!"), std::string(out.c_str(), out.length()));
+}
+
+using std::string;
+using std::map;
+using std::set;
+
+TEST(LibRadosAio, OmapPP) {
+ Rados cluster;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ std::string pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+ IoCtx ioctx;
+ cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+ string header_str = "baz";
+ bufferptr bp(header_str.c_str(), header_str.size() + 1);
+ bufferlist header_to_set;
+ header_to_set.push_back(bp);
+ map<string, bufferlist> to_set;
+ {
+ boost::scoped_ptr<AioCompletion> my_completion(cluster.aio_create_completion(0, 0));
+ ObjectWriteOperation op;
+ to_set["foo"] = header_to_set;
+ to_set["foo2"] = header_to_set;
+ to_set["qfoo3"] = header_to_set;
+ op.omap_set(to_set);
+
+ op.omap_set_header(header_to_set);
+
+ ioctx.aio_operate("test_obj", my_completion.get(), &op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion->get_return_value());
+ }
+
+ {
+ boost::scoped_ptr<AioCompletion> my_completion(cluster.aio_create_completion(0, 0));
+ ObjectReadOperation op;
+ map<string, pair<bufferlist, int> > assertions;
+ bufferlist val;
+ val.append(string("bar"));
+ assertions["foo"] = pair<bufferlist, int>(val, CEPH_OSD_CMPXATTR_OP_EQ);
+
+ int r;
+ op.omap_cmp(assertions, &r);
+
+ ioctx.aio_operate("test_obj", my_completion.get(), &op, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ EXPECT_EQ(-ECANCELED, my_completion->get_return_value());
+ ASSERT_EQ(-ECANCELED, r);
+ }
+
+ {
+ boost::scoped_ptr<AioCompletion> my_completion(cluster.aio_create_completion(0, 0));
+ ObjectReadOperation op;
+
+ set<string> set_got;
+ map<string, bufferlist> map_got;
+
+ set<string> to_get;
+ map<string, bufferlist> got3;
+
+ map<string, bufferlist> got4;
+
+ bufferlist header;
+
+ op.omap_get_keys2("", 1, &set_got, nullptr, 0);
+ op.omap_get_vals2("foo", 1, &map_got, nullptr, 0);
+
+ to_get.insert("foo");
+ to_get.insert("qfoo3");
+ op.omap_get_vals_by_keys(to_get, &got3, 0);
+
+ op.omap_get_header(&header, 0);
+
+ op.omap_get_vals2("foo2", "q", 1, &got4, nullptr, 0);
+
+ ioctx.aio_operate("test_obj", my_completion.get(), &op, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion->get_return_value());
+
+ ASSERT_EQ(header.length(), header_to_set.length());
+ ASSERT_EQ(set_got.size(), (unsigned)1);
+ ASSERT_EQ(*set_got.begin(), "foo");
+ ASSERT_EQ(map_got.size(), (unsigned)1);
+ ASSERT_EQ(map_got.begin()->first, "foo2");
+ ASSERT_EQ(got3.size(), (unsigned)2);
+ ASSERT_EQ(got3.begin()->first, "foo");
+ ASSERT_EQ(got3.rbegin()->first, "qfoo3");
+ ASSERT_EQ(got4.size(), (unsigned)1);
+ ASSERT_EQ(got4.begin()->first, "qfoo3");
+ }
+
+ {
+ boost::scoped_ptr<AioCompletion> my_completion(cluster.aio_create_completion(0, 0));
+ ObjectWriteOperation op;
+ set<string> to_remove;
+ to_remove.insert("foo2");
+ op.omap_rm_keys(to_remove);
+ ioctx.aio_operate("test_obj", my_completion.get(), &op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion->get_return_value());
+ }
+
+ {
+ boost::scoped_ptr<AioCompletion> my_completion(cluster.aio_create_completion(0, 0));
+ ObjectReadOperation op;
+
+ set<string> set_got;
+ op.omap_get_keys2("", -1, &set_got, nullptr, 0);
+ ioctx.aio_operate("test_obj", my_completion.get(), &op, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion->get_return_value());
+ ASSERT_EQ(set_got.size(), (unsigned)2);
+ }
+
+ {
+ boost::scoped_ptr<AioCompletion> my_completion(cluster.aio_create_completion(0, 0));
+ ObjectWriteOperation op;
+ op.omap_clear();
+ ioctx.aio_operate("test_obj", my_completion.get(), &op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion->get_return_value());
+ }
+
+ {
+ boost::scoped_ptr<AioCompletion> my_completion(cluster.aio_create_completion(0, 0));
+ ObjectReadOperation op;
+
+ set<string> set_got;
+ op.omap_get_keys2("", -1, &set_got, nullptr, 0);
+ ioctx.aio_operate("test_obj", my_completion.get(), &op, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion->get_return_value());
+ ASSERT_EQ(set_got.size(), (unsigned)0);
+ }
+
+ // omap_clear clears header *and* keys
+ {
+ boost::scoped_ptr<AioCompletion> my_completion(cluster.aio_create_completion(0, 0));
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("some data");
+ map<string,bufferlist> to_set;
+ to_set["foo"] = bl;
+ to_set["foo2"] = bl;
+ to_set["qfoo3"] = bl;
+ op.omap_set(to_set);
+ op.omap_set_header(bl);
+ ioctx.aio_operate("foo3", my_completion.get(), &op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion->get_return_value());
+ }
+ {
+ boost::scoped_ptr<AioCompletion> my_completion(cluster.aio_create_completion(0, 0));
+ ObjectWriteOperation op;
+ op.omap_clear();
+ ioctx.aio_operate("foo3", my_completion.get(), &op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion->get_return_value());
+ }
+ {
+ boost::scoped_ptr<AioCompletion> my_completion(cluster.aio_create_completion(0, 0));
+ ObjectReadOperation op;
+ set<string> set_got;
+ bufferlist hdr;
+ op.omap_get_keys2("", -1, &set_got, nullptr, 0);
+ op.omap_get_header(&hdr, NULL);
+ ioctx.aio_operate("foo3", my_completion.get(), &op, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion->get_return_value());
+ ASSERT_EQ(set_got.size(), (unsigned)0);
+ ASSERT_EQ(hdr.length(), 0u);
+ }
+
+ ioctx.remove("test_obj");
+ destroy_one_pool_pp(pool_name, cluster);
+}
+
+TEST(LibRadosAio, MultiWritePP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+
+ char buf2[64];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion2.get(),
+ bl2, sizeof(buf2), sizeof(buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+
+ bufferlist bl3;
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion3);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion3.get(),
+ &bl3, (sizeof(buf) + sizeof(buf2) * 3), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ ASSERT_EQ((int)(sizeof(buf) + sizeof(buf2)), my_completion3->get_return_value());
+ ASSERT_EQ(sizeof(buf) + sizeof(buf2), bl3.length());
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(bl3.c_str() + sizeof(buf), buf2, sizeof(buf2)));
+}
+
+TEST(LibRadosAio, AioUnlockPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, test_data.m_ioctx.lock_exclusive(test_data.m_oid, "TestLock", "Cookie", "", NULL, 0));
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_unlock(test_data.m_oid, "TestLock", "Cookie", my_completion.get()));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ ASSERT_EQ(0, test_data.m_ioctx.lock_exclusive(test_data.m_oid, "TestLock", "Cookie", "", NULL, 0));
+}
+
+class AioTestDataECPP
+{
+public:
+ AioTestDataECPP()
+ : m_init(false),
+ m_oid("foo")
+ {}
+
+ ~AioTestDataECPP()
+ {
+ if (m_init) {
+ m_ioctx.close();
+ destroy_one_ec_pool_pp(m_pool_name, m_cluster);
+ }
+ }
+
+ std::string init()
+ {
+ int ret;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ m_pool_name = get_temp_pool_name(pool_prefix);
+ std::string err = create_one_ec_pool_pp(m_pool_name, m_cluster);
+ if (!err.empty()) {
+ ostringstream oss;
+ oss << "create_one_ec_pool(" << m_pool_name << ") failed: error " << err;
+ return oss.str();
+ }
+ ret = m_cluster.ioctx_create(m_pool_name.c_str(), m_ioctx);
+ if (ret) {
+ destroy_one_ec_pool_pp(m_pool_name, m_cluster);
+ ostringstream oss;
+ oss << "rados_ioctx_create failed: error " << ret;
+ return oss.str();
+ }
+ m_oid = fmt::format("oid_{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ m_init = true;
+ return "";
+ }
+
+ Rados m_cluster;
+ IoCtx m_ioctx;
+ std::string m_pool_name;
+ bool m_init;
+ std::string m_oid;
+};
+
+// EC test cases
+TEST(LibRadosAioEC, SimpleWritePP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ {
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ }
+
+ {
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ test_data.m_ioctx.set_namespace("nspace");
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ }
+}
+
+TEST(LibRadosAioEC, WaitForSafePP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ ASSERT_EQ(0, my_completion->get_return_value());
+}
+
+TEST(LibRadosAioEC, RoundTripPP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+TEST(LibRadosAioEC, RoundTripPP2) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+//using ObjectWriteOperation/ObjectReadOperation with iohint
+TEST(LibRadosAioEC, RoundTripPP3)
+{
+ SKIP_IF_CRIMSON();
+ Rados cluster;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ std::string pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+ IoCtx ioctx;
+ cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+ auto my_completion1 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};;
+ ObjectWriteOperation op;
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf);
+
+ op.write(0, bl);
+ op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ioctx.aio_operate("test_obj", my_completion1.get(), &op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion1->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion1->get_return_value());
+
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ bl.clear();
+ ObjectReadOperation op1;
+ op1.read(0, sizeof(buf), &bl, NULL);
+ op1.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED|LIBRADOS_OP_FLAG_FADVISE_RANDOM);
+ ioctx.aio_operate("test_obj", my_completion2.get(), &op1, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(0, memcmp(buf, bl.c_str(), sizeof(buf)));
+
+ ioctx.remove("test_obj");
+ destroy_one_pool_pp(pool_name, cluster);
+}
+
+TEST(LibRadosAio, RoundTripAppendPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_append(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ char buf2[128];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_append(test_data.m_oid, my_completion2.get(),
+ bl2, sizeof(buf2)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ bufferlist bl3;
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion3);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion3.get(),
+ &bl3, 2 * sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ ASSERT_EQ((int)(sizeof(buf) * 2), my_completion3->get_return_value());
+ ASSERT_EQ(sizeof(buf) * 2, bl3.length());
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(bl3.c_str() + sizeof(buf), buf2, sizeof(buf2)));
+}
+
+TEST(LibRadosAioPP, RemoveTestPP) {
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ ASSERT_EQ(0, test_data.m_ioctx.append(test_data.m_oid, bl1, sizeof(buf)));
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_remove(test_data.m_oid, my_completion.get()));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ ASSERT_EQ(-ENOENT, test_data.m_ioctx.read(test_data.m_oid, bl2, sizeof(buf), 0));
+}
+
+TEST(LibRadosAioEC, RoundTripSparseReadPP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+
+ map<uint64_t, uint64_t> extents;
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_sparse_read(test_data.m_oid, my_completion2.get(),
+ &extents, &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ assert_eq_sparse(bl1, extents, bl2);
+}
+
+TEST(LibRadosAioEC, RoundTripAppendPP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ bool req;
+ ASSERT_EQ(0, test_data.m_ioctx.pool_requires_alignment2(&req));
+ ASSERT_TRUE(req);
+ uint64_t alignment;
+ ASSERT_EQ(0, test_data.m_ioctx.pool_required_alignment2(&alignment));
+ ASSERT_NE((unsigned)0, alignment);
+ int bsize = alignment;
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ bufferlist bl1;
+ bl1.append(buf, bsize);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_append(test_data.m_oid, my_completion.get(),
+ bl1, bsize));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+
+ int hbsize = bsize / 2;
+ char *buf2 = (char *)new char[hbsize];
+ memset(buf2, 0xdd, hbsize);
+ bufferlist bl2;
+ bl2.append(buf2, hbsize);
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_append(test_data.m_oid, my_completion2.get(),
+ bl2, hbsize));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion3);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_append(test_data.m_oid, my_completion3.get(),
+ bl2, hbsize));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ EXPECT_EQ(-EOPNOTSUPP, my_completion3->get_return_value());
+
+ bufferlist bl3;
+ auto my_completion4 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion4);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion4.get(),
+ &bl3, bsize * 3, 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion4->wait_for_complete());
+ }
+ int tbsize = bsize + hbsize;
+ ASSERT_EQ(tbsize, my_completion4->get_return_value());
+ ASSERT_EQ((unsigned)tbsize, bl3.length());
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, bsize));
+ ASSERT_EQ(0, memcmp(bl3.c_str() + bsize, buf2, hbsize));
+ delete[] buf;
+ delete[] buf2;
+}
+
+TEST(LibRadosAioEC, IsCompletePP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+
+ // Busy-wait until the AIO completes.
+ // Normally we wouldn't do this, but we want to test is_complete.
+ while (true) {
+ int is_complete = my_completion2->is_complete();
+ if (is_complete)
+ break;
+ }
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+TEST(LibRadosAioEC, IsSafePP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+
+ // Busy-wait until the AIO completes.
+ // Normally we wouldn't do this, but we want to test rados_aio_is_safe.
+ while (true) {
+ int is_complete = my_completion->is_complete();
+ if (is_complete)
+ break;
+ }
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ bufferlist bl2;
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+TEST(LibRadosAioEC, ReturnValuePP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ bufferlist bl1;
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read("nonexistent", my_completion.get(),
+ &bl1, 128, 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(-ENOENT, my_completion->get_return_value());
+}
+
+TEST(LibRadosAioEC, FlushPP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xee, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_flush());
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+TEST(LibRadosAioEC, FlushAsyncPP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ auto flush_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xee, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_flush_async(flush_completion.get()));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, flush_completion->wait_for_complete());
+ }
+ ASSERT_EQ(1, my_completion->is_complete());
+ ASSERT_EQ(1, flush_completion->is_complete());
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion2.get(),
+ &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl2.length());
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+TEST(LibRadosAioEC, RoundTripWriteFullPP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ char buf2[64];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write_full(test_data.m_oid, my_completion2.get(), bl2));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ bufferlist bl3;
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion3);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion3.get(),
+ &bl3, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf2), my_completion3->get_return_value());
+ ASSERT_EQ(sizeof(buf2), bl3.length());
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf2, sizeof(buf2)));
+}
+
+//using ObjectWriteOperation/ObjectReadOperation with iohint
+TEST(LibRadosAioEC, RoundTripWriteFullPP2)
+{
+ SKIP_IF_CRIMSON();
+ Rados cluster;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ std::string pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+ IoCtx ioctx;
+ cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+ auto my_completion1 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ObjectWriteOperation op;
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf);
+
+ op.write_full(bl);
+ op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_NOCACHE);
+ ioctx.aio_operate("test_obj", my_completion1.get(), &op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion1->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion1->get_return_value());
+
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ bl.clear();
+ ObjectReadOperation op1;
+ op1.read(0, sizeof(buf), &bl, NULL);
+ op1.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_NOCACHE|LIBRADOS_OP_FLAG_FADVISE_RANDOM);
+ ioctx.aio_operate("test_obj", my_completion2.get(), &op1, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ EXPECT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(0, memcmp(buf, bl.c_str(), sizeof(buf)));
+
+ ioctx.remove("test_obj");
+ destroy_one_pool_pp(pool_name, cluster);
+}
+
+TEST(LibRadosAioEC, SimpleStatPP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ uint64_t psize;
+ time_t pmtime;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_stat(test_data.m_oid, my_completion2.get(),
+ &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), psize);
+}
+
+TEST(LibRadosAioEC, SimpleStatPPNS) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ uint64_t psize;
+ time_t pmtime;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_stat(test_data.m_oid, my_completion2.get(),
+ &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), psize);
+}
+
+TEST(LibRadosAioEC, StatRemovePP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ uint64_t psize;
+ time_t pmtime;
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_stat(test_data.m_oid, my_completion2.get(),
+ &psize, &pmtime));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(sizeof(buf), psize);
+ uint64_t psize2;
+ time_t pmtime2;
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion3);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_remove(test_data.m_oid, my_completion3.get()));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion3->get_return_value());
+
+ auto my_completion4 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion4);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_stat(test_data.m_oid, my_completion4.get(),
+ &psize2, &pmtime2));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion4->wait_for_complete());
+ }
+ ASSERT_EQ(-ENOENT, my_completion4->get_return_value());
+}
+
+TEST(LibRadosAioEC, ExecuteClassPP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ bufferlist in, out;
+ ASSERT_EQ(0, test_data.m_ioctx.aio_exec(test_data.m_oid, my_completion2.get(),
+ "hello", "say_hello", in, &out));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+ ASSERT_EQ(std::string("Hello, world!"), std::string(out.c_str(), out.length()));
+}
+
+TEST(LibRadosAioEC, OmapPP) {
+ SKIP_IF_CRIMSON();
+ Rados cluster;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ std::string pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_ec_pool_pp(pool_name, cluster));
+ IoCtx ioctx;
+ cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+ string header_str = "baz";
+ bufferptr bp(header_str.c_str(), header_str.size() + 1);
+ bufferlist header_to_set;
+ header_to_set.push_back(bp);
+ map<string, bufferlist> to_set;
+ {
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ObjectWriteOperation op;
+ to_set["foo"] = header_to_set;
+ to_set["foo2"] = header_to_set;
+ to_set["qfoo3"] = header_to_set;
+ op.omap_set(to_set);
+
+ op.omap_set_header(header_to_set);
+
+ ioctx.aio_operate("test_obj", my_completion.get(), &op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ EXPECT_EQ(-EOPNOTSUPP, my_completion->get_return_value());
+ }
+ ioctx.remove("test_obj");
+ destroy_one_ec_pool_pp(pool_name, cluster);
+}
+
+TEST(LibRadosAioEC, MultiWritePP) {
+ SKIP_IF_CRIMSON();
+ AioTestDataECPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+
+ char buf2[64];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion2.get(),
+ bl2, sizeof(buf2), sizeof(buf)));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(-EOPNOTSUPP, my_completion2->get_return_value());
+
+ bufferlist bl3;
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion3);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read(test_data.m_oid, my_completion3.get(),
+ &bl3, (sizeof(buf) + sizeof(buf2) * 3), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), my_completion3->get_return_value());
+ ASSERT_EQ(sizeof(buf), bl3.length());
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, sizeof(buf)));
+
+}
+
+TEST(LibRadosAio, RacingRemovePP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init({{"objecter_retry_writes_after_first_reply", "true"}}));
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion2);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_remove(test_data.m_oid, my_completion2.get()));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ my_completion2->wait_for_complete();
+ my_completion->wait_for_complete();
+ }
+ ASSERT_EQ(-ENOENT, my_completion2->get_return_value());
+ ASSERT_EQ(0, my_completion->get_return_value());
+ ASSERT_EQ(0, test_data.m_ioctx.stat(test_data.m_oid, nullptr, nullptr));
+}
+
+TEST(LibRadosAio, RoundTripCmpExtPP) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char full[128];
+ memset(full, 0xcc, sizeof(full));
+ bufferlist bl1;
+ bl1.append(full, sizeof(full));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write(test_data.m_oid, my_completion.get(),
+ bl1, sizeof(full), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+
+ /* compare with match */
+ bufferlist cbl;
+ cbl.append(full, sizeof(full));
+ auto my_completion2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_cmpext(test_data.m_oid, my_completion2.get(), 0, cbl));
+
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion2->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion2->get_return_value());
+
+ /* compare with mismatch */
+ memset(full, 0xdd, sizeof(full));
+ cbl.clear();
+ cbl.append(full, sizeof(full));
+ auto my_completion3 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_EQ(0, test_data.m_ioctx.aio_cmpext(test_data.m_oid, my_completion3.get(), 0, cbl));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion3->wait_for_complete());
+ }
+ ASSERT_EQ(-MAX_ERRNO, my_completion3->get_return_value());
+}
+
+TEST(LibRadosAio, RoundTripCmpExtPP2)
+{
+ int ret;
+ char buf[128];
+ char miscmp_buf[128];
+ bufferlist cbl;
+ Rados cluster;
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_info()->name());
+ std::string pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+ IoCtx ioctx;
+ cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+ auto wr_cmpl = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ObjectWriteOperation wr_op;
+ memset(buf, 0xcc, sizeof(buf));
+ memset(miscmp_buf, 0xdd, sizeof(miscmp_buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ wr_op.write_full(bl);
+ wr_op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ioctx.aio_operate("test_obj", wr_cmpl.get(), &wr_op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, wr_cmpl->wait_for_complete());
+ }
+ EXPECT_EQ(0, wr_cmpl->get_return_value());
+
+ /* cmpext as write op. first match then mismatch */
+ auto wr_cmpext_cmpl = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ cbl.append(buf, sizeof(buf));
+ ret = 0;
+
+ wr_op.cmpext(0, cbl, &ret);
+ wr_op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ioctx.aio_operate("test_obj", wr_cmpext_cmpl.get(), &wr_op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, wr_cmpext_cmpl->wait_for_complete());
+ }
+ EXPECT_EQ(0, wr_cmpext_cmpl->get_return_value());
+ EXPECT_EQ(0, ret);
+
+ auto wr_cmpext_cmpl2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ cbl.clear();
+ cbl.append(miscmp_buf, sizeof(miscmp_buf));
+ ret = 0;
+
+ wr_op.cmpext(0, cbl, &ret);
+ wr_op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ioctx.aio_operate("test_obj", wr_cmpext_cmpl2.get(), &wr_op);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, wr_cmpext_cmpl2->wait_for_complete());
+ }
+ EXPECT_EQ(-MAX_ERRNO, wr_cmpext_cmpl2->get_return_value());
+ EXPECT_EQ(-MAX_ERRNO, ret);
+
+ /* cmpext as read op */
+ auto rd_cmpext_cmpl = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ObjectReadOperation rd_op;
+ cbl.clear();
+ cbl.append(buf, sizeof(buf));
+ ret = 0;
+ rd_op.cmpext(0, cbl, &ret);
+ rd_op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ioctx.aio_operate("test_obj", rd_cmpext_cmpl.get(), &rd_op, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rd_cmpext_cmpl->wait_for_complete());
+ }
+ EXPECT_EQ(0, rd_cmpext_cmpl->get_return_value());
+ EXPECT_EQ(0, ret);
+
+ auto rd_cmpext_cmpl2 = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ cbl.clear();
+ cbl.append(miscmp_buf, sizeof(miscmp_buf));
+ ret = 0;
+
+ rd_op.cmpext(0, cbl, &ret);
+ rd_op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ioctx.aio_operate("test_obj", rd_cmpext_cmpl2.get(), &rd_op, 0);
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rd_cmpext_cmpl2->wait_for_complete());
+ }
+ EXPECT_EQ(-MAX_ERRNO, rd_cmpext_cmpl2->get_return_value());
+ EXPECT_EQ(-MAX_ERRNO, ret);
+
+ ioctx.remove("test_obj");
+ destroy_one_pool_pp(pool_name, cluster);
+}
+
+ceph::mutex my_lock = ceph::make_mutex("my_lock");
+set<unsigned> inflight;
+unsigned max_success = 0;
+unsigned min_failed = 0;
+
+struct io_info {
+ unsigned i;
+ AioCompletion *c;
+};
+
+void pool_io_callback(completion_t cb, void *arg /* Actually AioCompletion* */)
+{
+ io_info *info = (io_info *)arg;
+ unsigned long i = info->i;
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, info->c->wait_for_complete());
+ }
+ int r = info->c->get_return_value();
+ //cout << "finish " << i << " r = " << r << std::endl;
+
+ std::scoped_lock l(my_lock);
+ inflight.erase(i);
+ if (r == 0) {
+ if (i > max_success) {
+ max_success = i;
+ }
+ } else {
+ if (!min_failed || i < min_failed) {
+ min_failed = i;
+ }
+ }
+}
+
+TEST(LibRadosAio, PoolEIOFlag) {
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+
+ bufferlist bl;
+ bl.append("some data");
+ std::thread *t = nullptr;
+
+ unsigned max = 100;
+ unsigned timeout = max * 10;
+ unsigned long i = 1;
+ my_lock.lock();
+ for (; min_failed == 0 && i <= timeout; ++i) {
+ io_info *info = new io_info;
+ info->i = i;
+ info->c = Rados::aio_create_completion();
+ info->c->set_complete_callback((void*)info, pool_io_callback);
+ inflight.insert(i);
+ my_lock.unlock();
+ int r = test_data.m_ioctx.aio_write(test_data.m_oid, info->c, bl, bl.length(), 0);
+ //cout << "start " << i << " r = " << r << std::endl;
+
+ if (i == max / 2) {
+ cout << "setting pool EIO" << std::endl;
+ t = new std::thread(
+ [&] {
+ bufferlist empty;
+ ASSERT_EQ(0, test_data.m_cluster.mon_command(
+ fmt::format(R"({{
+ "prefix": "osd pool set",
+ "pool": "{}",
+ "var": "eio",
+ "val": "true"
+ }})", test_data.m_pool_name),
+ empty, nullptr, nullptr));
+ });
+ }
+
+ std::this_thread::sleep_for(10ms);
+ my_lock.lock();
+ if (r < 0) {
+ inflight.erase(i);
+ break;
+ }
+ }
+ t->join();
+ delete t;
+
+ // wait for ios to finish
+ for (; !inflight.empty(); ++i) {
+ cout << "waiting for " << inflight.size() << std::endl;
+ my_lock.unlock();
+ sleep(1);
+ my_lock.lock();
+ }
+
+ cout << "max_success " << max_success << ", min_failed " << min_failed << std::endl;
+ ASSERT_TRUE(max_success + 1 == min_failed);
+ my_lock.unlock();
+}
+
+// This test case reproduces https://tracker.ceph.com/issues/57152
+TEST(LibRadosAio, MultiReads) {
+
+ // here we test multithreaded aio reads
+
+ AioTestDataPP test_data;
+ ASSERT_EQ("", test_data.init());
+ auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(my_completion);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, test_data.m_ioctx.aio_write("foo", my_completion.get(),
+ bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+
+ // Don't use std::vector to store bufferlists (e.g for parallelizing aio_reads),
+ // as they are being moved whenever the vector resizes
+ // and will cause invalidated references.
+ std::deque<std::pair<bufferlist, std::unique_ptr<AioCompletion>>> reads;
+ for (int i = 0; i < 100; i++) {
+ // std::deque is appropriate here as emplace_back() is obliged to
+ // preserve the referenced inserted element. (Unlike insert() or erase())
+ auto& [bl, aiocp] = reads.emplace_back();
+ aiocp = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()};
+ ASSERT_TRUE(aiocp);
+ ASSERT_EQ(0, test_data.m_ioctx.aio_read("foo", aiocp.get(),
+ &bl, sizeof(buf), 0));
+ }
+ for (auto& [bl, aiocp] : reads) {
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, aiocp->wait_for_complete());
+ }
+ ASSERT_EQ((int)sizeof(buf), aiocp->get_return_value());
+ ASSERT_EQ(0, memcmp(buf, bl.c_str(), sizeof(buf)));
+ }
+}
diff --git a/src/test/librados/asio.cc b/src/test/librados/asio.cc
new file mode 100644
index 000000000..9f86b4472
--- /dev/null
+++ b/src/test/librados/asio.cc
@@ -0,0 +1,369 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ */
+
+#include "librados/librados_asio.h"
+#include <gtest/gtest.h>
+
+#include "common/ceph_argparse.h"
+#include "common/debug.h"
+#include "common/errno.h"
+#include "global/global_init.h"
+
+#include <boost/range/begin.hpp>
+#include <boost/range/end.hpp>
+#include <spawn/spawn.hpp>
+#include <boost/asio/use_future.hpp>
+
+#define dout_subsys ceph_subsys_rados
+#define dout_context g_ceph_context
+
+using namespace std;
+
+// test fixture for global setup/teardown
+class AsioRados : public ::testing::Test {
+ static constexpr auto poolname = "ceph_test_rados_api_asio";
+
+ protected:
+ static librados::Rados rados;
+ static librados::IoCtx io;
+ // writes to snapio fail immediately with -EROFS. this is used to test errors
+ // that come from inside the initiating function, rather than passed to the
+ // AioCompletion callback
+ static librados::IoCtx snapio;
+
+ public:
+ static void SetUpTestCase() {
+ ASSERT_EQ(0, rados.init_with_context(g_ceph_context));
+ ASSERT_EQ(0, rados.connect());
+ // open/create test pool
+ int r = rados.ioctx_create(poolname, io);
+ if (r == -ENOENT) {
+ r = rados.pool_create(poolname);
+ if (r == -EEXIST) {
+ r = 0;
+ } else if (r == 0) {
+ r = rados.ioctx_create(poolname, io);
+ }
+ }
+ ASSERT_EQ(0, r);
+ ASSERT_EQ(0, rados.ioctx_create(poolname, snapio));
+ snapio.snap_set_read(1);
+ // initialize the "exist" object
+ bufferlist bl;
+ bl.append("hello");
+ ASSERT_EQ(0, io.write_full("exist", bl));
+ }
+
+ static void TearDownTestCase() {
+ rados.shutdown();
+ }
+};
+librados::Rados AsioRados::rados;
+librados::IoCtx AsioRados::io;
+librados::IoCtx AsioRados::snapio;
+
+TEST_F(AsioRados, AsyncReadCallback)
+{
+ boost::asio::io_service service;
+
+ auto success_cb = [&] (boost::system::error_code ec, bufferlist bl) {
+ EXPECT_FALSE(ec);
+ EXPECT_EQ("hello", bl.to_str());
+ };
+ librados::async_read(service, io, "exist", 256, 0, success_cb);
+
+ auto failure_cb = [&] (boost::system::error_code ec, bufferlist bl) {
+ EXPECT_EQ(boost::system::errc::no_such_file_or_directory, ec);
+ };
+ librados::async_read(service, io, "noexist", 256, 0, failure_cb);
+
+ service.run();
+}
+
+TEST_F(AsioRados, AsyncReadFuture)
+{
+ boost::asio::io_service service;
+
+ std::future<bufferlist> f1 = librados::async_read(service, io, "exist", 256,
+ 0, boost::asio::use_future);
+ std::future<bufferlist> f2 = librados::async_read(service, io, "noexist", 256,
+ 0, boost::asio::use_future);
+
+ service.run();
+
+ EXPECT_NO_THROW({
+ auto bl = f1.get();
+ EXPECT_EQ("hello", bl.to_str());
+ });
+ EXPECT_THROW(f2.get(), boost::system::system_error);
+}
+
+TEST_F(AsioRados, AsyncReadYield)
+{
+ boost::asio::io_service service;
+
+ auto success_cr = [&] (spawn::yield_context yield) {
+ boost::system::error_code ec;
+ auto bl = librados::async_read(service, io, "exist", 256, 0, yield[ec]);
+ EXPECT_FALSE(ec);
+ EXPECT_EQ("hello", bl.to_str());
+ };
+ spawn::spawn(service, success_cr);
+
+ auto failure_cr = [&] (spawn::yield_context yield) {
+ boost::system::error_code ec;
+ auto bl = librados::async_read(service, io, "noexist", 256, 0, yield[ec]);
+ EXPECT_EQ(boost::system::errc::no_such_file_or_directory, ec);
+ };
+ spawn::spawn(service, failure_cr);
+
+ service.run();
+}
+
+TEST_F(AsioRados, AsyncWriteCallback)
+{
+ boost::asio::io_service service;
+
+ bufferlist bl;
+ bl.append("hello");
+
+ auto success_cb = [&] (boost::system::error_code ec) {
+ EXPECT_FALSE(ec);
+ };
+ librados::async_write(service, io, "exist", bl, bl.length(), 0,
+ success_cb);
+
+ auto failure_cb = [&] (boost::system::error_code ec) {
+ EXPECT_EQ(boost::system::errc::read_only_file_system, ec);
+ };
+ librados::async_write(service, snapio, "exist", bl, bl.length(), 0,
+ failure_cb);
+
+ service.run();
+}
+
+TEST_F(AsioRados, AsyncWriteFuture)
+{
+ boost::asio::io_service service;
+
+ bufferlist bl;
+ bl.append("hello");
+
+ auto f1 = librados::async_write(service, io, "exist", bl, bl.length(), 0,
+ boost::asio::use_future);
+ auto f2 = librados::async_write(service, snapio, "exist", bl, bl.length(), 0,
+ boost::asio::use_future);
+
+ service.run();
+
+ EXPECT_NO_THROW(f1.get());
+ EXPECT_THROW(f2.get(), boost::system::system_error);
+}
+
+TEST_F(AsioRados, AsyncWriteYield)
+{
+ boost::asio::io_service service;
+
+ bufferlist bl;
+ bl.append("hello");
+
+ auto success_cr = [&] (spawn::yield_context yield) {
+ boost::system::error_code ec;
+ librados::async_write(service, io, "exist", bl, bl.length(), 0,
+ yield[ec]);
+ EXPECT_FALSE(ec);
+ EXPECT_EQ("hello", bl.to_str());
+ };
+ spawn::spawn(service, success_cr);
+
+ auto failure_cr = [&] (spawn::yield_context yield) {
+ boost::system::error_code ec;
+ librados::async_write(service, snapio, "exist", bl, bl.length(), 0,
+ yield[ec]);
+ EXPECT_EQ(boost::system::errc::read_only_file_system, ec);
+ };
+ spawn::spawn(service, failure_cr);
+
+ service.run();
+}
+
+TEST_F(AsioRados, AsyncReadOperationCallback)
+{
+ boost::asio::io_service service;
+ {
+ librados::ObjectReadOperation op;
+ op.read(0, 0, nullptr, nullptr);
+ auto success_cb = [&] (boost::system::error_code ec, bufferlist bl) {
+ EXPECT_FALSE(ec);
+ EXPECT_EQ("hello", bl.to_str());
+ };
+ librados::async_operate(service, io, "exist", &op, 0, success_cb);
+ }
+ {
+ librados::ObjectReadOperation op;
+ op.read(0, 0, nullptr, nullptr);
+ auto failure_cb = [&] (boost::system::error_code ec, bufferlist bl) {
+ EXPECT_EQ(boost::system::errc::no_such_file_or_directory, ec);
+ };
+ librados::async_operate(service, io, "noexist", &op, 0, failure_cb);
+ }
+ service.run();
+}
+
+TEST_F(AsioRados, AsyncReadOperationFuture)
+{
+ boost::asio::io_service service;
+ std::future<bufferlist> f1;
+ {
+ librados::ObjectReadOperation op;
+ op.read(0, 0, nullptr, nullptr);
+ f1 = librados::async_operate(service, io, "exist", &op, 0,
+ boost::asio::use_future);
+ }
+ std::future<bufferlist> f2;
+ {
+ librados::ObjectReadOperation op;
+ op.read(0, 0, nullptr, nullptr);
+ f2 = librados::async_operate(service, io, "noexist", &op, 0,
+ boost::asio::use_future);
+ }
+ service.run();
+
+ EXPECT_NO_THROW({
+ auto bl = f1.get();
+ EXPECT_EQ("hello", bl.to_str());
+ });
+ EXPECT_THROW(f2.get(), boost::system::system_error);
+}
+
+TEST_F(AsioRados, AsyncReadOperationYield)
+{
+ boost::asio::io_service service;
+
+ auto success_cr = [&] (spawn::yield_context yield) {
+ librados::ObjectReadOperation op;
+ op.read(0, 0, nullptr, nullptr);
+ boost::system::error_code ec;
+ auto bl = librados::async_operate(service, io, "exist", &op, 0,
+ yield[ec]);
+ EXPECT_FALSE(ec);
+ EXPECT_EQ("hello", bl.to_str());
+ };
+ spawn::spawn(service, success_cr);
+
+ auto failure_cr = [&] (spawn::yield_context yield) {
+ librados::ObjectReadOperation op;
+ op.read(0, 0, nullptr, nullptr);
+ boost::system::error_code ec;
+ auto bl = librados::async_operate(service, io, "noexist", &op, 0,
+ yield[ec]);
+ EXPECT_EQ(boost::system::errc::no_such_file_or_directory, ec);
+ };
+ spawn::spawn(service, failure_cr);
+
+ service.run();
+}
+
+TEST_F(AsioRados, AsyncWriteOperationCallback)
+{
+ boost::asio::io_service service;
+
+ bufferlist bl;
+ bl.append("hello");
+
+ {
+ librados::ObjectWriteOperation op;
+ op.write_full(bl);
+ auto success_cb = [&] (boost::system::error_code ec) {
+ EXPECT_FALSE(ec);
+ };
+ librados::async_operate(service, io, "exist", &op, 0, success_cb);
+ }
+ {
+ librados::ObjectWriteOperation op;
+ op.write_full(bl);
+ auto failure_cb = [&] (boost::system::error_code ec) {
+ EXPECT_EQ(boost::system::errc::read_only_file_system, ec);
+ };
+ librados::async_operate(service, snapio, "exist", &op, 0, failure_cb);
+ }
+ service.run();
+}
+
+TEST_F(AsioRados, AsyncWriteOperationFuture)
+{
+ boost::asio::io_service service;
+
+ bufferlist bl;
+ bl.append("hello");
+
+ std::future<void> f1;
+ {
+ librados::ObjectWriteOperation op;
+ op.write_full(bl);
+ f1 = librados::async_operate(service, io, "exist", &op, 0,
+ boost::asio::use_future);
+ }
+ std::future<void> f2;
+ {
+ librados::ObjectWriteOperation op;
+ op.write_full(bl);
+ f2 = librados::async_operate(service, snapio, "exist", &op, 0,
+ boost::asio::use_future);
+ }
+ service.run();
+
+ EXPECT_NO_THROW(f1.get());
+ EXPECT_THROW(f2.get(), boost::system::system_error);
+}
+
+TEST_F(AsioRados, AsyncWriteOperationYield)
+{
+ boost::asio::io_service service;
+
+ bufferlist bl;
+ bl.append("hello");
+
+ auto success_cr = [&] (spawn::yield_context yield) {
+ librados::ObjectWriteOperation op;
+ op.write_full(bl);
+ boost::system::error_code ec;
+ librados::async_operate(service, io, "exist", &op, 0, yield[ec]);
+ EXPECT_FALSE(ec);
+ };
+ spawn::spawn(service, success_cr);
+
+ auto failure_cr = [&] (spawn::yield_context yield) {
+ librados::ObjectWriteOperation op;
+ op.write_full(bl);
+ boost::system::error_code ec;
+ librados::async_operate(service, snapio, "exist", &op, 0, yield[ec]);
+ EXPECT_EQ(boost::system::errc::read_only_file_system, ec);
+ };
+ spawn::spawn(service, failure_cr);
+
+ service.run();
+}
+
+int main(int argc, char **argv)
+{
+ auto args = argv_to_vec(argc, argv);
+ env_to_vec(args);
+
+ auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT,
+ CODE_ENVIRONMENT_UTILITY, 0);
+ common_init_finish(cct.get());
+
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/src/test/librados/c_read_operations.cc b/src/test/librados/c_read_operations.cc
new file mode 100644
index 000000000..a8bad0748
--- /dev/null
+++ b/src/test/librados/c_read_operations.cc
@@ -0,0 +1,895 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// Tests for the C API coverage of atomic read operations
+
+#include <cstring> // For memcpy
+#include <errno.h>
+#include <string>
+
+#include "include/buffer.h"
+#include "include/denc.h"
+#include "include/err.h"
+#include "include/rados/librados.h"
+#include "include/rbd/features.h" // For RBD_FEATURES_ALL
+#include "include/scope_guard.h"
+#include "test/librados/TestCase.h"
+#include "test/librados/test.h"
+
+const char *data = "testdata";
+const char *obj = "testobj";
+const size_t len = strlen(data);
+
+class CReadOpsTest : public RadosTest {
+protected:
+ void write_object() {
+ // Create an object and write to it
+ ASSERT_EQ(0, rados_write(ioctx, obj, data, len, 0));
+ }
+ void remove_object() {
+ ASSERT_EQ(0, rados_remove(ioctx, obj));
+ }
+ int cmp_xattr(const char *xattr, const char *value, size_t value_len,
+ uint8_t cmp_op)
+ {
+ rados_read_op_t op = rados_create_read_op();
+ rados_read_op_cmpxattr(op, xattr, cmp_op, value, value_len);
+ int r = rados_read_op_operate(op, ioctx, obj, 0);
+ rados_release_read_op(op);
+ return r;
+ }
+
+ void fetch_and_verify_omap_vals(char const* const* keys,
+ char const* const* vals,
+ const size_t *lens,
+ size_t len)
+ {
+ rados_omap_iter_t iter_vals, iter_keys, iter_vals_by_key;
+ int r_vals, r_keys, r_vals_by_key;
+ rados_read_op_t op = rados_create_read_op();
+ rados_read_op_omap_get_vals2(op, NULL, NULL, 100, &iter_vals, NULL, &r_vals);
+ rados_read_op_omap_get_keys2(op, NULL, 100, &iter_keys, NULL, &r_keys);
+ rados_read_op_omap_get_vals_by_keys(op, keys, len,
+ &iter_vals_by_key, &r_vals_by_key);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+ ASSERT_EQ(0, r_vals);
+ ASSERT_EQ(0, r_keys);
+ ASSERT_EQ(0, r_vals_by_key);
+
+ const char *zeros[len];
+ size_t zero_lens[len];
+ memset(zeros, 0, sizeof(zeros));
+ memset(zero_lens, 0, sizeof(zero_lens));
+ compare_omap_vals(keys, vals, lens, len, iter_vals);
+ compare_omap_vals(keys, zeros, zero_lens, len, iter_keys);
+ compare_omap_vals(keys, vals, lens, len, iter_vals_by_key);
+ }
+
+ void compare_omap_vals(char const* const* keys,
+ char const* const* vals,
+ const size_t *lens,
+ size_t len,
+ rados_omap_iter_t iter)
+ {
+ size_t i = 0;
+ char *key = NULL;
+ char *val = NULL;
+ size_t val_len = 0;
+ ASSERT_EQ(len, rados_omap_iter_size(iter));
+ while (i < len) {
+ ASSERT_EQ(0, rados_omap_get_next(iter, &key, &val, &val_len));
+ if (val_len == 0 && key == NULL && val == NULL)
+ break;
+ if (key)
+ EXPECT_EQ(std::string(keys[i]), std::string(key));
+ else
+ EXPECT_EQ(keys[i], key);
+ ASSERT_EQ(0, memcmp(vals[i], val, val_len));
+ ASSERT_EQ(lens[i], val_len);
+ ++i;
+ }
+ ASSERT_EQ(i, len);
+ ASSERT_EQ(0, rados_omap_get_next(iter, &key, &val, &val_len));
+ ASSERT_EQ((char*)NULL, key);
+ ASSERT_EQ((char*)NULL, val);
+ ASSERT_EQ(0u, val_len);
+ rados_omap_get_end(iter);
+ }
+
+ // these two used to test omap funcs that accept length for both keys and vals
+ void fetch_and_verify_omap_vals2(char const* const* keys,
+ char const* const* vals,
+ const size_t *keylens,
+ const size_t *vallens,
+ size_t len)
+ {
+ rados_omap_iter_t iter_vals_by_key;
+ int r_vals_by_key;
+ rados_read_op_t op = rados_create_read_op();
+ rados_read_op_omap_get_vals_by_keys2(op, keys, len, keylens,
+ &iter_vals_by_key, &r_vals_by_key);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+ ASSERT_EQ(0, r_vals_by_key);
+
+ compare_omap_vals2(keys, vals, keylens, vallens, len, iter_vals_by_key);
+ }
+
+ void compare_omap_vals2(char const* const* keys,
+ char const* const* vals,
+ const size_t *keylens,
+ const size_t *vallens,
+ size_t len,
+ rados_omap_iter_t iter)
+ {
+ size_t i = 0;
+ char *key = NULL;
+ char *val = NULL;
+ size_t key_len = 0;
+ size_t val_len = 0;
+ ASSERT_EQ(len, rados_omap_iter_size(iter));
+ while (i < len) {
+ ASSERT_EQ(0, rados_omap_get_next2(iter, &key, &val, &key_len, &val_len));
+ if (key_len == 0 && val_len == 0 && key == NULL && val == NULL)
+ break;
+ if (key)
+ EXPECT_EQ(std::string(keys[i], keylens[i]), std::string(key, key_len));
+ else
+ EXPECT_EQ(keys[i], key);
+ ASSERT_EQ(val_len, vallens[i]);
+ ASSERT_EQ(key_len, keylens[i]);
+ ASSERT_EQ(0, memcmp(vals[i], val, val_len));
+ ++i;
+ }
+ ASSERT_EQ(i, len);
+ ASSERT_EQ(0, rados_omap_get_next2(iter, &key, &val, &key_len, &val_len));
+ ASSERT_EQ((char*)NULL, key);
+ ASSERT_EQ((char*)NULL, val);
+ ASSERT_EQ(0u, key_len);
+ ASSERT_EQ(0u, val_len);
+ rados_omap_get_end(iter);
+ }
+
+ void compare_xattrs(char const* const* keys,
+ char const* const* vals,
+ const size_t *lens,
+ size_t len,
+ rados_xattrs_iter_t iter)
+ {
+ size_t i = 0;
+ char *key = NULL;
+ char *val = NULL;
+ size_t val_len = 0;
+ while (i < len) {
+ ASSERT_EQ(0, rados_getxattrs_next(iter, (const char**) &key,
+ (const char**) &val, &val_len));
+ if (key == NULL)
+ break;
+ EXPECT_EQ(std::string(keys[i]), std::string(key));
+ if (val != NULL) {
+ EXPECT_EQ(0, memcmp(vals[i], val, val_len));
+ }
+ EXPECT_EQ(lens[i], val_len);
+ ++i;
+ }
+ ASSERT_EQ(i, len);
+ ASSERT_EQ(0, rados_getxattrs_next(iter, (const char**)&key,
+ (const char**)&val, &val_len));
+ ASSERT_EQ((char*)NULL, key);
+ ASSERT_EQ((char*)NULL, val);
+ ASSERT_EQ(0u, val_len);
+ rados_getxattrs_end(iter);
+ }
+};
+
+TEST_F(CReadOpsTest, NewDelete) {
+ rados_read_op_t op = rados_create_read_op();
+ ASSERT_TRUE(op);
+ rados_release_read_op(op);
+}
+
+TEST_F(CReadOpsTest, SetOpFlags) {
+ write_object();
+
+ rados_read_op_t op = rados_create_read_op();
+ size_t bytes_read = 0;
+ char *out = NULL;
+ int rval = 0;
+ rados_read_op_exec(op, "rbd", "get_id", NULL, 0, &out,
+ &bytes_read, &rval);
+ rados_read_op_set_flags(op, LIBRADOS_OP_FLAG_FAILOK);
+ EXPECT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ EXPECT_EQ(-EIO, rval);
+ EXPECT_EQ(0u, bytes_read);
+ EXPECT_EQ((char*)NULL, out);
+ rados_release_read_op(op);
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, AssertExists) {
+ rados_read_op_t op = rados_create_read_op();
+ rados_read_op_assert_exists(op);
+
+ ASSERT_EQ(-ENOENT, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+
+ op = rados_create_read_op();
+ rados_read_op_assert_exists(op);
+
+ rados_completion_t completion;
+ ASSERT_EQ(0, rados_aio_create_completion(NULL, NULL, NULL, &completion));
+ auto sg = make_scope_guard([&] { rados_aio_release(completion); });
+ ASSERT_EQ(0, rados_aio_read_op_operate(op, ioctx, completion, obj, 0));
+ rados_aio_wait_for_complete(completion);
+ ASSERT_EQ(-ENOENT, rados_aio_get_return_value(completion));
+ rados_release_read_op(op);
+
+ write_object();
+
+ op = rados_create_read_op();
+ rados_read_op_assert_exists(op);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, AssertVersion) {
+ write_object();
+ // Write to the object a second time to guarantee that its
+ // version number is greater than 0
+ write_object();
+ uint64_t v = rados_get_last_version(ioctx);
+
+ rados_read_op_t op = rados_create_read_op();
+ rados_read_op_assert_version(op, v+1);
+ ASSERT_EQ(-EOVERFLOW, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+
+ op = rados_create_read_op();
+ rados_read_op_assert_version(op, v-1);
+ ASSERT_EQ(-ERANGE, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+
+ op = rados_create_read_op();
+ rados_read_op_assert_version(op, v);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, CmpXattr) {
+ write_object();
+
+ char buf[len];
+ memset(buf, 0xcc, sizeof(buf));
+
+ const char *xattr = "test";
+ rados_setxattr(ioctx, obj, xattr, buf, sizeof(buf));
+
+ // equal value
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_EQ));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_NE));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_GT));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_GTE));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_LT));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_LTE));
+
+ // < value
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, sizeof(buf) - 1, LIBRADOS_CMPXATTR_OP_EQ));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, sizeof(buf) - 1, LIBRADOS_CMPXATTR_OP_NE));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, sizeof(buf) - 1, LIBRADOS_CMPXATTR_OP_GT));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, sizeof(buf) - 1, LIBRADOS_CMPXATTR_OP_GTE));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, sizeof(buf) - 1, LIBRADOS_CMPXATTR_OP_LT));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, sizeof(buf) - 1, LIBRADOS_CMPXATTR_OP_LTE));
+
+ // > value
+ memset(buf, 0xcd, sizeof(buf));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_EQ));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_NE));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_GT));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_GTE));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_LT));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, sizeof(buf), LIBRADOS_CMPXATTR_OP_LTE));
+
+ // check that null bytes are compared correctly
+ rados_setxattr(ioctx, obj, xattr, "\0\0", 2);
+ buf[0] = '\0';
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_EQ));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_NE));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_GT));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_GTE));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_LT));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_LTE));
+
+ buf[1] = '\0';
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_EQ));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_NE));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_GT));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_GTE));
+ EXPECT_EQ(-ECANCELED, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_LT));
+ EXPECT_EQ(1, cmp_xattr(xattr, buf, 2, LIBRADOS_CMPXATTR_OP_LTE));
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, Read) {
+ write_object();
+
+ char buf[len];
+ // check that using read_ops returns the same data with
+ // or without bytes_read and rval out params
+ {
+ rados_read_op_t op = rados_create_read_op();
+ rados_read_op_read(op, 0, len, buf, NULL, NULL);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(0, memcmp(data, buf, len));
+ rados_release_read_op(op);
+ }
+
+ {
+ rados_read_op_t op = rados_create_read_op();
+ int rval;
+ rados_read_op_read(op, 0, len, buf, NULL, &rval);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(data, buf, len));
+ rados_release_read_op(op);
+ }
+
+ {
+ rados_read_op_t op = rados_create_read_op();
+ size_t bytes_read = 0;
+ rados_read_op_read(op, 0, len, buf, &bytes_read, NULL);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(len, bytes_read);
+ ASSERT_EQ(0, memcmp(data, buf, len));
+ rados_release_read_op(op);
+ }
+
+ {
+ rados_read_op_t op = rados_create_read_op();
+ size_t bytes_read = 0;
+ int rval;
+ rados_read_op_read(op, 0, len, buf, &bytes_read, &rval);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(len, bytes_read);
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(data, buf, len));
+ rados_release_read_op(op);
+ }
+
+ {
+ rados_read_op_t op = rados_create_read_op();
+ size_t bytes_read = 0;
+ int rval;
+ rados_read_op_read(op, 0, len, buf, &bytes_read, &rval);
+ rados_read_op_set_flags(op, LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(len, bytes_read);
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(data, buf, len));
+ rados_release_read_op(op);
+ }
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, Checksum) {
+ write_object();
+
+ {
+ rados_read_op_t op = rados_create_read_op();
+ ceph_le64 init_value(-1);
+ rados_read_op_checksum(op, LIBRADOS_CHECKSUM_TYPE_XXHASH64,
+ reinterpret_cast<char *>(&init_value),
+ sizeof(init_value), 0, len, 0, NULL, 0, NULL);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+ }
+
+ {
+ ceph_le32 init_value(-1);
+ ceph_le32 crc[2];
+ rados_read_op_t op = rados_create_read_op();
+ rados_read_op_checksum(op, LIBRADOS_CHECKSUM_TYPE_CRC32C,
+ reinterpret_cast<char *>(&init_value),
+ sizeof(init_value), 0, len, 0,
+ reinterpret_cast<char *>(&crc), sizeof(crc),
+ nullptr);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(1U, crc[0]);
+ uint32_t expected_crc = ceph_crc32c(
+ -1, reinterpret_cast<const uint8_t*>(data), static_cast<uint32_t>(len));
+ ASSERT_EQ(expected_crc, crc[1]);
+ rados_release_read_op(op);
+ }
+
+ {
+ ceph_le32 init_value(-1);
+ int rval;
+ rados_read_op_t op = rados_create_read_op();
+ rados_read_op_checksum(op, LIBRADOS_CHECKSUM_TYPE_XXHASH32,
+ reinterpret_cast<char *>(&init_value),
+ sizeof(init_value), 0, len, 0, nullptr, 0, &rval);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(0, rval);
+ rados_release_read_op(op);
+ }
+
+ {
+ ceph_le32 init_value(-1);
+ ceph_le32 crc[3];
+ int rval;
+ rados_read_op_t op = rados_create_read_op();
+ rados_read_op_checksum(op, LIBRADOS_CHECKSUM_TYPE_CRC32C,
+ reinterpret_cast<char *>(&init_value),
+ sizeof(init_value), 0, len, 4,
+ reinterpret_cast<char *>(&crc), sizeof(crc), &rval);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(2U, crc[0]);
+ uint32_t expected_crc[2];
+ expected_crc[0] = ceph_crc32c(
+ -1, reinterpret_cast<const uint8_t*>(data), 4U);
+ expected_crc[1] = ceph_crc32c(
+ -1, reinterpret_cast<const uint8_t*>(data + 4), 4U);
+ ASSERT_EQ(expected_crc[0], crc[1]);
+ ASSERT_EQ(expected_crc[1], crc[2]);
+ ASSERT_EQ(0, rval);
+ rados_release_read_op(op);
+ }
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, RWOrderedRead) {
+ write_object();
+
+ char buf[len];
+ rados_read_op_t op = rados_create_read_op();
+ size_t bytes_read = 0;
+ int rval;
+ rados_read_op_read(op, 0, len, buf, &bytes_read, &rval);
+ rados_read_op_set_flags(op, LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj,
+ LIBRADOS_OPERATION_ORDER_READS_WRITES));
+ ASSERT_EQ(len, bytes_read);
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(data, buf, len));
+ rados_release_read_op(op);
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, ShortRead) {
+ write_object();
+
+ char buf[len * 2];
+ // check that using read_ops returns the same data with
+ // or without bytes_read and rval out params
+ {
+ rados_read_op_t op = rados_create_read_op();
+ rados_read_op_read(op, 0, len * 2, buf, NULL, NULL);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(0, memcmp(data, buf, len));
+ rados_release_read_op(op);
+ }
+
+ {
+ rados_read_op_t op = rados_create_read_op();
+ int rval;
+ rados_read_op_read(op, 0, len * 2, buf, NULL, &rval);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(data, buf, len));
+ rados_release_read_op(op);
+ }
+
+ {
+ rados_read_op_t op = rados_create_read_op();
+ size_t bytes_read = 0;
+ rados_read_op_read(op, 0, len * 2, buf, &bytes_read, NULL);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(len, bytes_read);
+ ASSERT_EQ(0, memcmp(data, buf, len));
+ rados_release_read_op(op);
+ }
+
+ {
+ rados_read_op_t op = rados_create_read_op();
+ size_t bytes_read = 0;
+ int rval;
+ rados_read_op_read(op, 0, len * 2, buf, &bytes_read, &rval);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(len, bytes_read);
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(data, buf, len));
+ rados_release_read_op(op);
+ }
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, Exec) {
+ // create object so we don't get -ENOENT
+ write_object();
+
+ rados_read_op_t op = rados_create_read_op();
+ ASSERT_TRUE(op);
+ size_t bytes_read = 0;
+ char *out = NULL;
+ int rval = 0;
+ rados_read_op_exec(op, "rbd", "get_all_features", NULL, 0, &out,
+ &bytes_read, &rval);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+ EXPECT_EQ(0, rval);
+ EXPECT_TRUE(out);
+ uint64_t features;
+ EXPECT_EQ(sizeof(features), bytes_read);
+ // make sure buffer is at least as long as it claims
+ bufferlist bl;
+ bl.append(out, bytes_read);
+ auto it = bl.cbegin();
+ ceph::decode(features, it);
+ ASSERT_EQ(RBD_FEATURES_ALL, features);
+ rados_buffer_free(out);
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, ExecUserBuf) {
+ // create object so we don't get -ENOENT
+ write_object();
+
+ rados_read_op_t op = rados_create_read_op();
+ size_t bytes_read = 0;
+ uint64_t features;
+ char out[sizeof(features)];
+ int rval = 0;
+ rados_read_op_exec_user_buf(op, "rbd", "get_all_features", NULL, 0, out,
+ sizeof(out), &bytes_read, &rval);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+ EXPECT_EQ(0, rval);
+ EXPECT_EQ(sizeof(features), bytes_read);
+
+ // buffer too short
+ bytes_read = 1024;
+ op = rados_create_read_op();
+ rados_read_op_exec_user_buf(op, "rbd", "get_all_features", NULL, 0, out,
+ sizeof(features) - 1, &bytes_read, &rval);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+ EXPECT_EQ(0u, bytes_read);
+ EXPECT_EQ(-ERANGE, rval);
+
+ // input buffer and no rval or bytes_read
+ op = rados_create_read_op();
+ rados_read_op_exec_user_buf(op, "rbd", "get_all_features", out, sizeof(out),
+ out, sizeof(out), NULL, NULL);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, Stat) {
+ rados_read_op_t op = rados_create_read_op();
+ uint64_t size = 1;
+ int rval = 0;
+ rados_read_op_stat(op, &size, NULL, &rval);
+ EXPECT_EQ(-ENOENT, rados_read_op_operate(op, ioctx, obj, 0));
+ EXPECT_EQ(-EIO, rval);
+ EXPECT_EQ(1u, size);
+ rados_release_read_op(op);
+
+ time_t ts = 1457129052;
+ rados_write_op_t wop = rados_create_write_op();
+ rados_write_op_write(wop, data, len, 0);
+ ASSERT_EQ(0, rados_write_op_operate(wop, ioctx, obj, &ts, 0));
+ rados_release_write_op(wop);
+
+ time_t ts2;
+ op = rados_create_read_op();
+ rados_read_op_stat(op, &size, &ts2, &rval);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ EXPECT_EQ(0, rval);
+ EXPECT_EQ(len, size);
+ EXPECT_EQ(ts2, ts);
+ rados_release_read_op(op);
+
+ op = rados_create_read_op();
+ rados_read_op_stat(op, NULL, NULL, NULL);
+ EXPECT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+
+ remove_object();
+
+ op = rados_create_read_op();
+ rados_read_op_stat(op, NULL, NULL, NULL);
+ EXPECT_EQ(-ENOENT, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+}
+
+TEST_F(CReadOpsTest, Stat2) {
+ rados_read_op_t op = rados_create_read_op();
+ uint64_t size = 1;
+ int rval = 0;
+ rados_read_op_stat2(op, &size, NULL, &rval);
+ EXPECT_EQ(-ENOENT, rados_read_op_operate(op, ioctx, obj, 0));
+ EXPECT_EQ(-EIO, rval);
+ EXPECT_EQ(1u, size);
+ rados_release_read_op(op);
+
+ struct timespec ts;
+ ts.tv_sec = 1457129052;
+ ts.tv_nsec = 123456789;
+ rados_write_op_t wop = rados_create_write_op();
+ rados_write_op_write(wop, data, len, 0);
+ ASSERT_EQ(0, rados_write_op_operate2(wop, ioctx, obj, &ts, 0));
+ rados_release_write_op(wop);
+
+ struct timespec ts2 = {};
+ op = rados_create_read_op();
+ rados_read_op_stat2(op, &size, &ts2, &rval);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ EXPECT_EQ(0, rval);
+ EXPECT_EQ(len, size);
+ EXPECT_EQ(ts2.tv_sec, ts.tv_sec);
+ EXPECT_EQ(ts2.tv_nsec, ts.tv_nsec);
+ rados_release_read_op(op);
+
+ op = rados_create_read_op();
+ rados_read_op_stat2(op, NULL, NULL, NULL);
+ EXPECT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+
+ remove_object();
+
+ op = rados_create_read_op();
+ rados_read_op_stat2(op, NULL, NULL, NULL);
+ EXPECT_EQ(-ENOENT, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+}
+
+TEST_F(CReadOpsTest, Omap) {
+ char *keys[] = {(char*)"bar",
+ (char*)"foo",
+ (char*)"test1",
+ (char*)"test2"};
+ char *vals[] = {(char*)"",
+ (char*)"\0",
+ (char*)"abc",
+ (char*)"va\0lue"};
+ size_t lens[] = {0, 1, 3, 6};
+
+ // check for -ENOENT before the object exists and when it exists
+ // with no omap entries
+ rados_omap_iter_t iter_vals;
+ rados_read_op_t rop = rados_create_read_op();
+ rados_read_op_omap_get_vals2(rop, "", "", 10, &iter_vals, NULL, NULL);
+ ASSERT_EQ(-ENOENT, rados_read_op_operate(rop, ioctx, obj, 0));
+ rados_release_read_op(rop);
+ compare_omap_vals(NULL, NULL, NULL, 0, iter_vals);
+
+ write_object();
+
+ fetch_and_verify_omap_vals(NULL, NULL, NULL, 0);
+
+ // write and check for the k/v pairs
+ rados_write_op_t op = rados_create_write_op();
+ rados_write_op_omap_set(op, keys, vals, lens, 4);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, obj, NULL, 0));
+ rados_release_write_op(op);
+
+ fetch_and_verify_omap_vals(keys, vals, lens, 4);
+
+ rados_omap_iter_t iter_keys;
+ int r_vals = -1, r_keys = -1;
+ rop = rados_create_read_op();
+ rados_read_op_omap_get_vals2(rop, "", "test", 1, &iter_vals, NULL, &r_vals);
+ rados_read_op_omap_get_keys2(rop, "test", 1, &iter_keys, NULL, &r_keys);
+ ASSERT_EQ(0, rados_read_op_operate(rop, ioctx, obj, 0));
+ rados_release_read_op(rop);
+ EXPECT_EQ(0, r_vals);
+ EXPECT_EQ(0, r_keys);
+ EXPECT_EQ(1u, rados_omap_iter_size(iter_vals));
+ EXPECT_EQ(1u, rados_omap_iter_size(iter_keys));
+
+ compare_omap_vals(&keys[2], &vals[2], &lens[2], 1, iter_vals);
+ compare_omap_vals(&keys[2], &vals[0], &lens[0], 1, iter_keys);
+
+ // check omap_cmp finds all expected values
+ rop = rados_create_read_op();
+ int rvals[4];
+ for (int i = 0; i < 4; ++i)
+ rados_read_op_omap_cmp(rop, keys[i], LIBRADOS_CMPXATTR_OP_EQ,
+ vals[i], lens[i], &rvals[i]);
+ EXPECT_EQ(0, rados_read_op_operate(rop, ioctx, obj, 0));
+ rados_release_read_op(rop);
+ for (int i = 0; i < 4; ++i)
+ EXPECT_EQ(0, rvals[i]);
+
+ // try to remove keys with a guard that should fail
+ op = rados_create_write_op();
+ rados_write_op_omap_cmp(op, keys[2], LIBRADOS_CMPXATTR_OP_LT,
+ vals[2], lens[2], &r_vals);
+ rados_write_op_omap_rm_keys(op, keys, 2);
+ EXPECT_EQ(-ECANCELED, rados_write_op_operate(op, ioctx, obj, NULL, 0));
+ rados_release_write_op(op);
+
+ // see http://tracker.ceph.com/issues/19518
+ //ASSERT_EQ(-ECANCELED, r_vals);
+
+ // verifying the keys are still there, and then remove them
+ op = rados_create_write_op();
+ rados_write_op_omap_cmp(op, keys[0], LIBRADOS_CMPXATTR_OP_EQ,
+ vals[0], lens[0], NULL);
+ rados_write_op_omap_cmp(op, keys[1], LIBRADOS_CMPXATTR_OP_EQ,
+ vals[1], lens[1], NULL);
+ rados_write_op_omap_rm_keys(op, keys, 2);
+ EXPECT_EQ(0, rados_write_op_operate(op, ioctx, obj, NULL, 0));
+ rados_release_write_op(op);
+
+ fetch_and_verify_omap_vals(&keys[2], &vals[2], &lens[2], 2);
+
+ // clear the rest and check there are none left
+ op = rados_create_write_op();
+ rados_write_op_omap_clear(op);
+ EXPECT_EQ(0, rados_write_op_operate(op, ioctx, obj, NULL, 0));
+ rados_release_write_op(op);
+
+ fetch_and_verify_omap_vals(NULL, NULL, NULL, 0);
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, OmapNuls) {
+ char *keys[] = {(char*)"1\0bar",
+ (char*)"2baar\0",
+ (char*)"3baa\0rr"};
+ char *vals[] = {(char*)"_\0var",
+ (char*)"_vaar\0",
+ (char*)"__vaa\0rr"};
+ size_t nklens[] = {5, 6, 7};
+ size_t nvlens[] = {5, 6, 8};
+ const int paircount = 3;
+
+ // check for -ENOENT before the object exists and when it exists
+ // with no omap entries
+ rados_omap_iter_t iter_vals;
+ rados_read_op_t rop = rados_create_read_op();
+ rados_read_op_omap_get_vals2(rop, "", "", 10, &iter_vals, NULL, NULL);
+ ASSERT_EQ(-ENOENT, rados_read_op_operate(rop, ioctx, obj, 0));
+ rados_release_read_op(rop);
+ compare_omap_vals(NULL, NULL, NULL, 0, iter_vals);
+
+ write_object();
+
+ fetch_and_verify_omap_vals(NULL, NULL, NULL, 0);
+
+ // write and check for the k/v pairs
+ rados_write_op_t op = rados_create_write_op();
+ rados_write_op_omap_set2(op, keys, vals, nklens, nvlens, paircount);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, obj, NULL, 0));
+ rados_release_write_op(op);
+
+ fetch_and_verify_omap_vals2(keys, vals, nklens, nvlens, paircount);
+
+ // check omap_cmp finds all expected values
+ rop = rados_create_read_op();
+ int rvals[4];
+ for (int i = 0; i < paircount; ++i)
+ rados_read_op_omap_cmp2(rop, keys[i], LIBRADOS_CMPXATTR_OP_EQ,
+ vals[i], nklens[i], nvlens[i], &rvals[i]);
+ EXPECT_EQ(0, rados_read_op_operate(rop, ioctx, obj, 0));
+ rados_release_read_op(rop);
+ for (int i = 0; i < paircount; ++i)
+ EXPECT_EQ(0, rvals[i]);
+
+ // try to remove keys with a guard that should fail
+ int r_vals = -1;
+ op = rados_create_write_op();
+ rados_write_op_omap_cmp2(op, keys[2], LIBRADOS_CMPXATTR_OP_LT,
+ vals[2], nklens[2], nvlens[2], &r_vals);
+ rados_write_op_omap_rm_keys(op, keys, 2);
+ EXPECT_EQ(-ECANCELED, rados_write_op_operate(op, ioctx, obj, NULL, 0));
+ rados_release_write_op(op);
+
+ // verifying the keys are still there, and then remove them
+ op = rados_create_write_op();
+ rados_write_op_omap_cmp2(op, keys[0], LIBRADOS_CMPXATTR_OP_EQ,
+ vals[0], nklens[0], nvlens[0], NULL);
+ rados_write_op_omap_cmp2(op, keys[1], LIBRADOS_CMPXATTR_OP_EQ,
+ vals[1], nklens[1], nvlens[1], NULL);
+ rados_write_op_omap_rm_keys2(op, keys, nklens, 2);
+ EXPECT_EQ(0, rados_write_op_operate(op, ioctx, obj, NULL, 0));
+ rados_release_write_op(op);
+
+ fetch_and_verify_omap_vals2(&keys[2], &vals[2], &nklens[2], &nvlens[2], 1);
+
+ // clear the rest and check there are none left
+ op = rados_create_write_op();
+ rados_write_op_omap_clear(op);
+ EXPECT_EQ(0, rados_write_op_operate(op, ioctx, obj, NULL, 0));
+ rados_release_write_op(op);
+
+ fetch_and_verify_omap_vals(NULL, NULL, NULL, 0);
+
+ remove_object();
+}
+TEST_F(CReadOpsTest, GetXattrs) {
+ write_object();
+
+ char *keys[] = {(char*)"bar",
+ (char*)"foo",
+ (char*)"test1",
+ (char*)"test2"};
+ char *vals[] = {(char*)"",
+ (char*)"\0",
+ (char*)"abc",
+ (char*)"va\0lue"};
+ size_t lens[] = {0, 1, 3, 6};
+
+ int rval = 1;
+ rados_read_op_t op = rados_create_read_op();
+ rados_xattrs_iter_t it;
+ rados_read_op_getxattrs(op, &it, &rval);
+ EXPECT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ EXPECT_EQ(0, rval);
+ rados_release_read_op(op);
+ compare_xattrs(keys, vals, lens, 0, it);
+
+ for (int i = 0; i < 4; ++i)
+ rados_setxattr(ioctx, obj, keys[i], vals[i], lens[i]);
+
+ rval = 1;
+ op = rados_create_read_op();
+ rados_read_op_getxattrs(op, &it, &rval);
+ EXPECT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ EXPECT_EQ(0, rval);
+ rados_release_read_op(op);
+ compare_xattrs(keys, vals, lens, 4, it);
+
+ remove_object();
+}
+
+TEST_F(CReadOpsTest, CmpExt) {
+ char buf[len];
+ size_t bytes_read = 0;
+ int cmpext_val = 0;
+ int read_val = 0;
+
+ write_object();
+
+ // cmpext with match should ensure that the following read is successful
+ rados_read_op_t op = rados_create_read_op();
+ ASSERT_TRUE(op);
+ // @obj, @data and @len correspond to object initialised by write_object()
+ rados_read_op_cmpext(op, data, len, 0, &cmpext_val);
+ rados_read_op_read(op, 0, len, buf, &bytes_read, &read_val);
+ ASSERT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0));
+ ASSERT_EQ(len, bytes_read);
+ ASSERT_EQ(0, memcmp(data, buf, len));
+ ASSERT_EQ(cmpext_val, 0);
+ rados_release_read_op(op);
+
+ // cmpext with mismatch should fail and fill mismatch_buf accordingly
+ memset(buf, 0, sizeof(buf));
+ bytes_read = 0;
+ cmpext_val = 0;
+ read_val = 0;
+ op = rados_create_read_op();
+ ASSERT_TRUE(op);
+ // @obj, @data and @len correspond to object initialised by write_object()
+ rados_read_op_cmpext(op, "mismatch", strlen("mismatch"), 0, &cmpext_val);
+ rados_read_op_read(op, 0, len, buf, &bytes_read, &read_val);
+ ASSERT_EQ(-MAX_ERRNO, rados_read_op_operate(op, ioctx, obj, 0));
+ rados_release_read_op(op);
+
+ ASSERT_EQ(-MAX_ERRNO, cmpext_val);
+
+ remove_object();
+}
diff --git a/src/test/librados/c_write_operations.cc b/src/test/librados/c_write_operations.cc
new file mode 100644
index 000000000..558d0942f
--- /dev/null
+++ b/src/test/librados/c_write_operations.cc
@@ -0,0 +1,279 @@
+// Tests for the C API coverage of atomic write operations
+
+#include <errno.h>
+#include "gtest/gtest.h"
+#include "include/err.h"
+#include "include/rados/librados.h"
+#include "test/librados/test.h"
+
+TEST(LibradosCWriteOps, NewDelete) {
+ rados_write_op_t op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_release_write_op(op);
+}
+
+TEST(LibRadosCWriteOps, assertExists) {
+ rados_t cluster;
+ rados_ioctx_t ioctx;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+ rados_write_op_t op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_assert_exists(op);
+
+ // -2, ENOENT
+ ASSERT_EQ(-2, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ rados_release_write_op(op);
+
+ rados_write_op_t op2 = rados_create_write_op();
+ ASSERT_TRUE(op2);
+ rados_write_op_assert_exists(op2);
+
+ rados_completion_t completion;
+ ASSERT_EQ(0, rados_aio_create_completion(NULL, NULL, NULL, &completion));
+ ASSERT_EQ(0, rados_aio_write_op_operate(op2, ioctx, completion, "test", NULL, 0));
+ rados_aio_wait_for_complete(completion);
+ ASSERT_EQ(-2, rados_aio_get_return_value(completion));
+ rados_aio_release(completion);
+
+ rados_ioctx_destroy(ioctx);
+ rados_release_write_op(op2);
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRadosCWriteOps, WriteOpAssertVersion) {
+ rados_t cluster;
+ rados_ioctx_t ioctx;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+ rados_write_op_t op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_create(op, LIBRADOS_CREATE_EXCLUSIVE, NULL);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ rados_release_write_op(op);
+
+ // Write to the object a second time to guarantee that its
+ // version number is greater than 0
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_write_full(op, "hi", 2);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ rados_release_write_op(op);
+
+ uint64_t v = rados_get_last_version(ioctx);
+
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_assert_version(op, v+1);
+ ASSERT_EQ(-EOVERFLOW, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ rados_release_write_op(op);
+
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_assert_version(op, v-1);
+ ASSERT_EQ(-ERANGE, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ rados_release_write_op(op);
+
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_assert_version(op, v);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ rados_release_write_op(op);
+
+ rados_ioctx_destroy(ioctx);
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRadosCWriteOps, Xattrs) {
+ rados_t cluster;
+ rados_ioctx_t ioctx;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+ // Create an object with an xattr
+ rados_write_op_t op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_create(op, LIBRADOS_CREATE_EXCLUSIVE, NULL);
+ rados_write_op_setxattr(op, "key", "value", 5);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ rados_release_write_op(op);
+
+ // Check that xattr exists, if it does, delete it.
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_create(op, LIBRADOS_CREATE_IDEMPOTENT, NULL);
+ rados_write_op_cmpxattr(op, "key", LIBRADOS_CMPXATTR_OP_EQ, "value", 5);
+ rados_write_op_rmxattr(op, "key");
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ rados_release_write_op(op);
+
+ // Check the xattr exits, if it does, add it again (will fail) with -125
+ // (ECANCELED)
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_cmpxattr(op, "key", LIBRADOS_CMPXATTR_OP_EQ, "value", 5);
+ rados_write_op_setxattr(op, "key", "value", 5);
+ ASSERT_EQ(-ECANCELED, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+
+ rados_release_write_op(op);
+ rados_ioctx_destroy(ioctx);
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRadosCWriteOps, Write) {
+ rados_t cluster;
+ rados_ioctx_t ioctx;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+ // Create an object, write and write full to it
+ rados_write_op_t op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_create(op, LIBRADOS_CREATE_EXCLUSIVE, NULL);
+ rados_write_op_write(op, "four", 4, 0);
+ rados_write_op_write_full(op, "hi", 2);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ char hi[4];
+ ASSERT_EQ(2, rados_read(ioctx, "test", hi, 4, 0));
+ rados_release_write_op(op);
+
+ //create write op with iohint
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_write_full(op, "ceph", 4);
+ rados_write_op_set_flags(op, LIBRADOS_OP_FLAG_FADVISE_NOCACHE);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ ASSERT_EQ(4, rados_read(ioctx, "test", hi, 4, 0));
+ rados_release_write_op(op);
+
+ // Truncate and append
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_truncate(op, 1);
+ rados_write_op_append(op, "hi", 2);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ ASSERT_EQ(3, rados_read(ioctx, "test", hi, 4, 0));
+ rados_release_write_op(op);
+
+ // zero and remove
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_zero(op, 0, 3);
+ rados_write_op_remove(op);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ // ENOENT
+ ASSERT_EQ(-2, rados_read(ioctx, "test", hi, 4, 0));
+ rados_release_write_op(op);
+
+ rados_ioctx_destroy(ioctx);
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRadosCWriteOps, Exec) {
+ rados_t cluster;
+ rados_ioctx_t ioctx;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+ int rval = 1;
+ rados_write_op_t op = rados_create_write_op();
+ rados_write_op_exec(op, "hello", "record_hello", "test", 4, &rval);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ rados_release_write_op(op);
+ ASSERT_EQ(0, rval);
+
+ char hi[100];
+ ASSERT_EQ(12, rados_read(ioctx, "test", hi, 100, 0));
+ hi[12] = '\0';
+ ASSERT_EQ(0, strcmp("Hello, test!", hi));
+
+ rados_ioctx_destroy(ioctx);
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRadosCWriteOps, WriteSame) {
+ rados_t cluster;
+ rados_ioctx_t ioctx;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+ // Create an object, write to it using writesame
+ rados_write_op_t op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_create(op, LIBRADOS_CREATE_EXCLUSIVE, NULL);
+ rados_write_op_writesame(op, "four", 4, 4 * 4, 0);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ char hi[4 * 4];
+ ASSERT_EQ(sizeof(hi), static_cast<std::size_t>(
+ rados_read(ioctx, "test", hi,sizeof(hi), 0)));
+ rados_release_write_op(op);
+ ASSERT_EQ(0, memcmp("fourfourfourfour", hi, sizeof(hi)));
+
+ // cleanup
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_remove(op);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ rados_release_write_op(op);
+
+ rados_ioctx_destroy(ioctx);
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRadosCWriteOps, CmpExt) {
+ rados_t cluster;
+ rados_ioctx_t ioctx;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ rados_ioctx_create(cluster, pool_name.c_str(), &ioctx);
+
+ // create an object, write to it using writesame
+ rados_write_op_t op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_create(op, LIBRADOS_CREATE_EXCLUSIVE, NULL);
+ rados_write_op_write(op, "four", 4, 0);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ rados_release_write_op(op);
+ char hi[4];
+ ASSERT_EQ(sizeof(hi), static_cast<std::size_t>(rados_read(ioctx, "test", hi, sizeof(hi), 0)));
+ ASSERT_EQ(0, memcmp("four", hi, sizeof(hi)));
+
+ // compare and overwrite on (expected) match
+ int val = 0;
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_cmpext(op, "four", 4, 0, &val);
+ rados_write_op_write(op, "five", 4, 0);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+ ASSERT_EQ(0, val);
+ rados_release_write_op(op);
+ ASSERT_EQ(sizeof(hi), static_cast<std::size_t>(rados_read(ioctx, "test", hi, sizeof(hi), 0)));
+ ASSERT_EQ(0, memcmp("five", hi, sizeof(hi)));
+
+ // compare and bail before write due to mismatch
+ val = 0;
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_cmpext(op, "four", 4, 0, &val);
+ rados_write_op_write(op, "six ", 4, 0);
+ ASSERT_EQ(-MAX_ERRNO - 1, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+
+ ASSERT_EQ(-MAX_ERRNO - 1, val);
+
+ // cleanup
+ op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_remove(op);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "test", NULL, 0));
+
+ rados_ioctx_destroy(ioctx);
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
diff --git a/src/test/librados/cls.cc b/src/test/librados/cls.cc
new file mode 100644
index 000000000..c4f24954d
--- /dev/null
+++ b/src/test/librados/cls.cc
@@ -0,0 +1,36 @@
+#include <errno.h>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "include/rados/librados.hpp"
+#include "test/librados/test_cxx.h"
+
+using namespace librados;
+using std::map;
+using std::ostringstream;
+using std::string;
+
+TEST(LibRadosCls, DNE) {
+ Rados cluster;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+ IoCtx ioctx;
+ cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+ // create an object
+ string oid = "foo";
+ bufferlist bl;
+ ASSERT_EQ(0, ioctx.write(oid, bl, bl.length(), 0));
+
+ // call a bogus class
+ ASSERT_EQ(-EOPNOTSUPP, ioctx.exec(oid, "doesnotexistasdfasdf", "method", bl, bl));
+
+ // call a bogus method on existent class
+ ASSERT_EQ(-EOPNOTSUPP, ioctx.exec(oid, "lock", "doesnotexistasdfasdfasdf", bl, bl));
+
+ ioctx.close();
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster));
+}
diff --git a/src/test/librados/cls_remote_reads.cc b/src/test/librados/cls_remote_reads.cc
new file mode 100644
index 000000000..4256c072f
--- /dev/null
+++ b/src/test/librados/cls_remote_reads.cc
@@ -0,0 +1,55 @@
+#include <set>
+#include <string>
+
+#include "common/ceph_json.h"
+#include "gtest/gtest.h"
+#include "test/librados/test_cxx.h"
+
+#include "crimson_utils.h"
+
+using namespace librados;
+
+TEST(ClsTestRemoteReads, TestGather) {
+ SKIP_IF_CRIMSON();
+ Rados cluster;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+ IoCtx ioctx;
+ cluster.ioctx_create(pool_name.c_str(), ioctx);
+
+ bufferlist in, out;
+ int object_size = 4096;
+ char buf[object_size];
+ memset(buf, 1, sizeof(buf));
+
+ // create source objects from which data are gathered
+ in.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write_full("src_object.1", in));
+ in.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write_full("src_object.2", in));
+ in.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write_full("src_object.3", in));
+
+ // construct JSON request passed to "test_gather" method, and in turn, to "test_read" method
+ JSONFormatter *formatter = new JSONFormatter(true);
+ formatter->open_object_section("foo");
+ std::set<std::string> src_objects;
+ src_objects.insert("src_object.1");
+ src_objects.insert("src_object.2");
+ src_objects.insert("src_object.3");
+ encode_json("src_objects", src_objects, formatter);
+ encode_json("cls", "test_remote_reads", formatter);
+ encode_json("method", "test_read", formatter);
+ encode_json("pool", pool_name, formatter);
+ formatter->close_section();
+ in.clear();
+ formatter->flush(in);
+
+ // create target object by combining data gathered from source objects using "test_read" method
+ ASSERT_EQ(0, ioctx.exec("tgt_object", "test_remote_reads", "test_gather", in, out));
+
+ // read target object and check its size
+ ASSERT_EQ(3*object_size, ioctx.read("tgt_object", out, 0, 0));
+
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster));
+}
diff --git a/src/test/librados/cmd.cc b/src/test/librados/cmd.cc
new file mode 100644
index 000000000..1d110f73b
--- /dev/null
+++ b/src/test/librados/cmd.cc
@@ -0,0 +1,229 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/rados/librados.h"
+#include "include/rados/librados.hpp"
+#include "include/stringify.h"
+#include "test/librados/test.h"
+
+#include "gtest/gtest.h"
+#include <errno.h>
+#include <condition_variable>
+#include <map>
+#include <sstream>
+#include <string>
+
+using std::cout;
+using std::list;
+using std::map;
+using std::ostringstream;
+using std::string;
+
+TEST(LibRadosCmd, MonDescribe) {
+ rados_t cluster;
+ ASSERT_EQ("", connect_cluster(&cluster));
+
+ char *buf, *st;
+ size_t buflen, stlen;
+ char *cmd[2];
+
+ cmd[1] = NULL;
+
+ cmd[0] = (char *)"{\"prefix\":\"get_command_descriptions\"}";
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ ASSERT_LT(0u, buflen);
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ cmd[0] = (char *)"get_command_descriptions";
+ ASSERT_EQ(-EINVAL, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ cmd[0] = (char *)"asdfqwer";
+ ASSERT_EQ(-EINVAL, rados_mon_command(cluster, (const char **)cmd, 1, "{}", 2, &buf, &buflen, &st, &stlen));
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ cmd[0] = (char *)"";
+ ASSERT_EQ(-EINVAL, rados_mon_command(cluster, (const char **)cmd, 1, "{}", 2, &buf, &buflen, &st, &stlen));
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ cmd[0] = (char *)"{}";
+ ASSERT_EQ(-EINVAL, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ cmd[0] = (char *)"{\"abc\":\"something\"}";
+ ASSERT_EQ(-EINVAL, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ cmd[0] = (char *)"{\"prefix\":\"\"}";
+ ASSERT_EQ(-EINVAL, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ cmd[0] = (char *)"{\"prefix\":\" \"}";
+ ASSERT_EQ(-EINVAL, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ cmd[0] = (char *)"{\"prefix\":\";;;,,,;;,,\"}";
+ ASSERT_EQ(-EINVAL, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ cmd[0] = (char *)"{\"prefix\":\"extra command\"}";
+ ASSERT_EQ(-EINVAL, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ cmd[0] = (char *)"{\"prefix\":\"quorum_status\"}";
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ ASSERT_LT(0u, buflen);
+ //ASSERT_LT(0u, stlen);
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+ rados_shutdown(cluster);
+}
+
+TEST(LibRadosCmd, OSDCmd) {
+ rados_t cluster;
+ ASSERT_EQ("", connect_cluster(&cluster));
+ int r;
+ char *buf, *st;
+ size_t buflen, stlen;
+ char *cmd[2];
+ cmd[1] = NULL;
+
+ // note: tolerate NXIO here in case the cluster is thrashing out underneath us.
+ cmd[0] = (char *)"asdfasdf";
+ r = rados_osd_command(cluster, 0, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen);
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+ ASSERT_TRUE(r == -22 || r == -ENXIO);
+ cmd[0] = (char *)"version";
+ r = rados_osd_command(cluster, 0, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen);
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+ ASSERT_TRUE(r == -22 || r == -ENXIO);
+ cmd[0] = (char *)"{\"prefix\":\"version\"}";
+ r = rados_osd_command(cluster, 0, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen);
+ ASSERT_TRUE((r == 0 && buflen > 0) || (r == -ENXIO && buflen == 0));
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+ rados_shutdown(cluster);
+}
+
+TEST(LibRadosCmd, PGCmd) {
+ rados_t cluster;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+
+ char *buf, *st;
+ size_t buflen, stlen;
+ char *cmd[2];
+ cmd[1] = NULL;
+
+ int64_t poolid = rados_pool_lookup(cluster, pool_name.c_str());
+ ASSERT_LT(0, poolid);
+
+ string pgid = stringify(poolid) + ".0";
+
+ cmd[0] = (char *)"asdfasdf";
+ // note: tolerate NXIO here in case the cluster is thrashing out underneath us.
+ int r = rados_pg_command(cluster, pgid.c_str(), (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen);
+ ASSERT_TRUE(r == -22 || r == -ENXIO);
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ // make sure the pg exists on the osd before we query it
+ rados_ioctx_t io;
+ rados_ioctx_create(cluster, pool_name.c_str(), &io);
+ for (int i=0; i<100; i++) {
+ string oid = "obj" + stringify(i);
+ ASSERT_EQ(-ENOENT, rados_stat(io, oid.c_str(), NULL, NULL));
+ }
+ rados_ioctx_destroy(io);
+
+ string qstr = "{\"prefix\":\"pg\", \"cmd\":\"query\", \"pgid\":\"" + pgid + "\"}";
+ cmd[0] = (char *)qstr.c_str();
+ // note: tolerate ENOENT/ENXIO here if hte osd is thrashing out underneath us
+ r = rados_pg_command(cluster, pgid.c_str(), (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen);
+ ASSERT_TRUE(r == 0 || r == -ENOENT || r == -ENXIO);
+
+ ASSERT_LT(0u, buflen);
+ rados_buffer_free(buf);
+ rados_buffer_free(st);
+
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+struct Log {
+ list<string> log;
+ std::condition_variable cond;
+ std::mutex lock;
+
+ bool contains(const string& str) {
+ std::lock_guard<std::mutex> l(lock);
+ for (list<string>::iterator p = log.begin(); p != log.end(); ++p) {
+ if (p->find(str) != std::string::npos)
+ return true;
+ }
+ return false;
+ }
+};
+
+void log_cb(void *arg,
+ const char *line,
+ const char *who, uint64_t stampsec, uint64_t stamp_nsec,
+ uint64_t seq, const char *level,
+ const char *msg) {
+ Log *l = static_cast<Log *>(arg);
+ std::lock_guard<std::mutex> locker(l->lock);
+ l->log.push_back(line);
+ l->cond.notify_all();
+ cout << "got: " << line << std::endl;
+}
+
+TEST(LibRadosCmd, WatchLog) {
+ rados_t cluster;
+ ASSERT_EQ("", connect_cluster(&cluster));
+ char *buf, *st;
+ char *cmd[2];
+ cmd[1] = NULL;
+ size_t buflen, stlen;
+ Log l;
+
+ ASSERT_EQ(0, rados_monitor_log(cluster, "info", log_cb, &l));
+ cmd[0] = (char *)"{\"prefix\":\"log\", \"logtext\":[\"onexx\"]}";
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ for (int i=0; !l.contains("onexx"); i++) {
+ ASSERT_TRUE(i<100);
+ sleep(1);
+ }
+ ASSERT_TRUE(l.contains("onexx"));
+
+ cmd[0] = (char *)"{\"prefix\":\"log\", \"logtext\":[\"twoxx\"]}";
+ ASSERT_EQ(0, rados_monitor_log(cluster, "err", log_cb, &l));
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ sleep(2);
+ ASSERT_FALSE(l.contains("twoxx"));
+
+ ASSERT_EQ(0, rados_monitor_log(cluster, "info", log_cb, &l));
+ cmd[0] = (char *)"{\"prefix\":\"log\", \"logtext\":[\"threexx\"]}";
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ for (int i=0; !l.contains("threexx"); i++) {
+ ASSERT_TRUE(i<100);
+ sleep(1);
+ }
+
+ ASSERT_EQ(0, rados_monitor_log(cluster, "info", NULL, NULL));
+ cmd[0] = (char *)"{\"prefix\":\"log\", \"logtext\":[\"fourxx\"]}";
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ sleep(2);
+ ASSERT_FALSE(l.contains("fourxx"));
+ rados_shutdown(cluster);
+}
diff --git a/src/test/librados/cmd_cxx.cc b/src/test/librados/cmd_cxx.cc
new file mode 100644
index 000000000..d67e2613b
--- /dev/null
+++ b/src/test/librados/cmd_cxx.cc
@@ -0,0 +1,92 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <errno.h>
+#include <condition_variable>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "include/rados/librados.hpp"
+#include "include/stringify.h"
+#include "test/librados/test_cxx.h"
+
+using namespace librados;
+using std::map;
+using std::ostringstream;
+using std::string;
+
+TEST(LibRadosCmd, MonDescribePP) {
+ Rados cluster;
+ ASSERT_EQ("", connect_cluster_pp(cluster));
+ bufferlist inbl, outbl;
+ string outs;
+ ASSERT_EQ(0, cluster.mon_command("{\"prefix\": \"get_command_descriptions\"}",
+ inbl, &outbl, &outs));
+ ASSERT_LT(0u, outbl.length());
+ ASSERT_LE(0u, outs.length());
+ cluster.shutdown();
+}
+
+TEST(LibRadosCmd, OSDCmdPP) {
+ Rados cluster;
+ ASSERT_EQ("", connect_cluster_pp(cluster));
+ int r;
+ bufferlist inbl, outbl;
+ string outs;
+ string cmd;
+
+ // note: tolerate NXIO here in case the cluster is thrashing out underneath us.
+ cmd = "asdfasdf";
+ r = cluster.osd_command(0, cmd, inbl, &outbl, &outs);
+ ASSERT_TRUE(r == -22 || r == -ENXIO);
+ cmd = "version";
+ r = cluster.osd_command(0, cmd, inbl, &outbl, &outs);
+ ASSERT_TRUE(r == -22 || r == -ENXIO);
+ cmd = "{\"prefix\":\"version\"}";
+ r = cluster.osd_command(0, cmd, inbl, &outbl, &outs);
+ ASSERT_TRUE((r == 0 && outbl.length() > 0) || (r == -ENXIO && outbl.length() == 0));
+ cluster.shutdown();
+}
+
+TEST(LibRadosCmd, PGCmdPP) {
+ Rados cluster;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+
+ int r;
+ bufferlist inbl, outbl;
+ string outs;
+ string cmd;
+
+ int64_t poolid = cluster.pool_lookup(pool_name.c_str());
+ ASSERT_LT(0, poolid);
+
+ string pgid = stringify(poolid) + ".0";
+
+ cmd = "asdfasdf";
+ // note: tolerate NXIO here in case the cluster is thrashing out underneath us.
+ r = cluster.pg_command(pgid.c_str(), cmd, inbl, &outbl, &outs);
+ ASSERT_TRUE(r == -22 || r == -ENXIO);
+
+ // make sure the pg exists on the osd before we query it
+ IoCtx io;
+ cluster.ioctx_create(pool_name.c_str(), io);
+ for (int i=0; i<100; i++) {
+ string oid = "obj" + stringify(i);
+ ASSERT_EQ(-ENOENT, io.stat(oid, NULL, NULL));
+ }
+ io.close();
+
+ cmd = "{\"prefix\":\"pg\", \"cmd\":\"query\", \"pgid\":\"" + pgid + "\"}";
+ // note: tolerate ENOENT/ENXIO here if hte osd is thrashing out underneath us
+ r = cluster.pg_command(pgid.c_str(), cmd, inbl, &outbl, &outs);
+ ASSERT_TRUE(r == 0 || r == -ENOENT || r == -ENXIO);
+
+ ASSERT_LT(0u, outbl.length());
+
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster));
+}
+
diff --git a/src/test/librados/completion_speed.cc b/src/test/librados/completion_speed.cc
new file mode 100644
index 000000000..708a58425
--- /dev/null
+++ b/src/test/librados/completion_speed.cc
@@ -0,0 +1,38 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/rados/librados.hpp"
+#include "common/ceph_context.h"
+#include "common/Finisher.h"
+#include "librados/AioCompletionImpl.h"
+
+
+constexpr int max_completions = 10'000'000;
+int completed = 0;
+auto cct = (new CephContext(CEPH_ENTITY_TYPE_CLIENT))->get();
+Finisher f(cct);
+
+void completion_cb(librados::completion_t cb, void* arg) {
+ auto c = static_cast<librados::AioCompletion*>(arg);
+ delete c;
+ if (++completed < max_completions) {
+ auto aio = librados::Rados::aio_create_completion();
+ aio->set_complete_callback(static_cast<void*>(aio), &completion_cb);
+ f.queue(new librados::C_AioComplete(aio->pc));
+ }
+}
+
+int main(void) {
+ auto aio = librados::Rados::aio_create_completion();
+ aio->set_complete_callback(static_cast<void*>(aio), &completion_cb);
+ f.queue(new librados::C_AioComplete(aio->pc));
+ f.start();
+
+ while (completed < max_completions)
+ f.wait_for_empty();
+
+ f.stop();
+
+ assert(completed == max_completions);
+ cct->put();
+}
diff --git a/src/test/librados/crimson_utils.h b/src/test/librados/crimson_utils.h
new file mode 100644
index 000000000..4adcb69d9
--- /dev/null
+++ b/src/test/librados/crimson_utils.h
@@ -0,0 +1,15 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#pragma once
+
+#include <cstdlib>
+
+static inline bool is_crimson_cluster() {
+ return getenv("CRIMSON_COMPAT") != nullptr;
+}
+
+#define SKIP_IF_CRIMSON() \
+ if (is_crimson_cluster()) { \
+ GTEST_SKIP() << "Not supported by crimson yet. Skipped"; \
+ }
diff --git a/src/test/librados/io.cc b/src/test/librados/io.cc
new file mode 100644
index 000000000..7814463d4
--- /dev/null
+++ b/src/test/librados/io.cc
@@ -0,0 +1,461 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*
+// vim: ts=8 sw=2 smarttab
+
+#include <climits>
+
+#include "include/rados/librados.h"
+#include "include/encoding.h"
+#include "include/err.h"
+#include "include/scope_guard.h"
+#include "test/librados/test.h"
+#include "test/librados/TestCase.h"
+
+#include <errno.h>
+#include "gtest/gtest.h"
+#include "crimson_utils.h"
+
+using std::string;
+
+typedef RadosTest LibRadosIo;
+typedef RadosTestEC LibRadosIoEC;
+
+TEST_F(LibRadosIo, SimpleWrite) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ rados_ioctx_set_namespace(ioctx, "nspace");
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+}
+
+TEST_F(LibRadosIo, TooBig) {
+ char buf[1] = { 0 };
+ ASSERT_EQ(-E2BIG, rados_write(ioctx, "A", buf, UINT_MAX, 0));
+ ASSERT_EQ(-E2BIG, rados_append(ioctx, "A", buf, UINT_MAX));
+ ASSERT_EQ(-E2BIG, rados_write_full(ioctx, "A", buf, UINT_MAX));
+ ASSERT_EQ(-E2BIG, rados_writesame(ioctx, "A", buf, sizeof(buf), UINT_MAX, 0));
+}
+
+TEST_F(LibRadosIo, ReadTimeout) {
+ char buf[128];
+ memset(buf, 'a', sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+
+ {
+ // set up a second client
+ rados_t cluster;
+ rados_ioctx_t ioctx;
+ ASSERT_EQ(0, rados_create(&cluster, "admin"));
+ 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, "rados_osd_op_timeout", "1")); // use any small value that will result in a timeout
+ ASSERT_EQ(0, rados_conf_set(cluster, "ms_inject_internal_delays", "2")); // create a 2 second delay
+ ASSERT_EQ(0, rados_connect(cluster));
+ ASSERT_EQ(0, rados_ioctx_create(cluster, pool_name.c_str(), &ioctx));
+ rados_ioctx_set_namespace(ioctx, nspace.c_str());
+
+ // then we show that the buffer is changed after rados_read returned
+ // with a timeout
+ for (int i=0; i<5; i++) {
+ char buf2[sizeof(buf)];
+ memset(buf2, 0, sizeof(buf2));
+ int err = rados_read(ioctx, "foo", buf2, sizeof(buf2), 0);
+ if (err == -110) {
+ int startIndex = 0;
+ // find the index until which librados already read the object before the timeout occurred
+ for (unsigned b=0; b<sizeof(buf); b++) {
+ if (buf2[b] != buf[b]) {
+ startIndex = b;
+ break;
+ }
+ }
+
+ // wait some time to give librados a change to do something
+ sleep(1);
+
+ // then check if the buffer was changed after the call
+ if (buf2[startIndex] == 'a') {
+ printf("byte at index %d was changed after the timeout to %d\n",
+ startIndex, (int)buf[startIndex]);
+ ASSERT_TRUE(0);
+ break;
+ }
+ } else {
+ printf("no timeout :/\n");
+ }
+ }
+ rados_ioctx_destroy(ioctx);
+ rados_shutdown(cluster);
+ }
+}
+
+
+TEST_F(LibRadosIo, RoundTrip) {
+ char buf[128];
+ char buf2[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ memset(buf2, 0, sizeof(buf2));
+ ASSERT_EQ((int)sizeof(buf2), rados_read(ioctx, "foo", buf2, sizeof(buf2), 0));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+
+ uint64_t off = 19;
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "bar", buf, sizeof(buf), off));
+ memset(buf2, 0, sizeof(buf2));
+ ASSERT_EQ((int)sizeof(buf2), rados_read(ioctx, "bar", buf2, sizeof(buf2), off));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+}
+
+TEST_F(LibRadosIo, Checksum) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+
+ uint32_t expected_crc = ceph_crc32c(-1, reinterpret_cast<const uint8_t*>(buf),
+ sizeof(buf));
+ ceph_le32 init_value(-1);
+ ceph_le32 crc[2];
+ ASSERT_EQ(0, rados_checksum(ioctx, "foo", LIBRADOS_CHECKSUM_TYPE_CRC32C,
+ reinterpret_cast<char*>(&init_value),
+ sizeof(init_value), sizeof(buf), 0, 0,
+ reinterpret_cast<char*>(&crc), sizeof(crc)));
+ ASSERT_EQ(1U, crc[0]);
+ ASSERT_EQ(expected_crc, crc[1]);
+}
+
+TEST_F(LibRadosIo, OverlappingWriteRoundTrip) {
+ char buf[128];
+ char buf2[64];
+ char buf3[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf2, sizeof(buf2), 0));
+ memset(buf3, 0xdd, sizeof(buf3));
+ ASSERT_EQ((int)sizeof(buf3), rados_read(ioctx, "foo", buf3, sizeof(buf3), 0));
+ ASSERT_EQ(0, memcmp(buf3, buf2, sizeof(buf2)));
+ ASSERT_EQ(0, memcmp(buf3 + sizeof(buf2), buf, sizeof(buf) - sizeof(buf2)));
+}
+
+TEST_F(LibRadosIo, WriteFullRoundTrip) {
+ char buf[128];
+ char buf2[64];
+ char buf3[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_write_full(ioctx, "foo", buf2, sizeof(buf2)));
+ memset(buf3, 0x00, sizeof(buf3));
+ ASSERT_EQ((int)sizeof(buf2), rados_read(ioctx, "foo", buf3, sizeof(buf3), 0));
+ ASSERT_EQ(0, memcmp(buf2, buf3, sizeof(buf2)));
+}
+
+TEST_F(LibRadosIo, AppendRoundTrip) {
+ char buf[64];
+ char buf2[64];
+ char buf3[sizeof(buf) + sizeof(buf2)];
+ memset(buf, 0xde, sizeof(buf));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, sizeof(buf)));
+ memset(buf2, 0xad, sizeof(buf2));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf2, sizeof(buf2)));
+ memset(buf3, 0, sizeof(buf3));
+ ASSERT_EQ((int)sizeof(buf3), rados_read(ioctx, "foo", buf3, sizeof(buf3), 0));
+ ASSERT_EQ(0, memcmp(buf3, buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(buf3 + sizeof(buf), buf2, sizeof(buf2)));
+}
+
+TEST_F(LibRadosIo, ZeroLenZero) {
+ rados_write_op_t op = rados_create_write_op();
+ ASSERT_TRUE(op);
+ rados_write_op_zero(op, 0, 0);
+ ASSERT_EQ(0, rados_write_op_operate(op, ioctx, "foo", NULL, 0));
+ rados_release_write_op(op);
+}
+
+TEST_F(LibRadosIo, TruncTest) {
+ char buf[128];
+ char buf2[sizeof(buf)];
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_trunc(ioctx, "foo", sizeof(buf) / 2));
+ memset(buf2, 0, sizeof(buf2));
+ ASSERT_EQ((int)(sizeof(buf)/2), rados_read(ioctx, "foo", buf2, sizeof(buf2), 0));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)/2));
+}
+
+TEST_F(LibRadosIo, RemoveTest) {
+ char buf[128];
+ char buf2[sizeof(buf)];
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_remove(ioctx, "foo"));
+ memset(buf2, 0, sizeof(buf2));
+ ASSERT_EQ(-ENOENT, rados_read(ioctx, "foo", buf2, sizeof(buf2), 0));
+}
+
+TEST_F(LibRadosIo, XattrsRoundTrip) {
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(-ENODATA, rados_getxattr(ioctx, "foo", attr1, buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_setxattr(ioctx, "foo", attr1, attr1_buf, sizeof(attr1_buf)));
+ ASSERT_EQ((int)sizeof(attr1_buf),
+ rados_getxattr(ioctx, "foo", attr1, buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(attr1_buf, buf, sizeof(attr1_buf)));
+}
+
+TEST_F(LibRadosIo, RmXattr) {
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(0,
+ rados_setxattr(ioctx, "foo", attr1, attr1_buf, sizeof(attr1_buf)));
+ ASSERT_EQ(0, rados_rmxattr(ioctx, "foo", attr1));
+ ASSERT_EQ(-ENODATA, rados_getxattr(ioctx, "foo", attr1, buf, sizeof(buf)));
+
+ // Test rmxattr on a removed object
+ char buf2[128];
+ char attr2[] = "attr2";
+ char attr2_buf[] = "foo bar baz";
+ memset(buf2, 0xbb, sizeof(buf2));
+ ASSERT_EQ(0, rados_write(ioctx, "foo_rmxattr", buf2, sizeof(buf2), 0));
+ ASSERT_EQ(0,
+ rados_setxattr(ioctx, "foo_rmxattr", attr2, attr2_buf, sizeof(attr2_buf)));
+ ASSERT_EQ(0, rados_remove(ioctx, "foo_rmxattr"));
+ ASSERT_EQ(-ENOENT, rados_rmxattr(ioctx, "foo_rmxattr", attr2));
+}
+
+TEST_F(LibRadosIo, XattrIter) {
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ char attr2[] = "attr2";
+ char attr2_buf[256];
+ for (size_t j = 0; j < sizeof(attr2_buf); ++j) {
+ attr2_buf[j] = j % 0xff;
+ }
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_setxattr(ioctx, "foo", attr1, attr1_buf, sizeof(attr1_buf)));
+ ASSERT_EQ(0, rados_setxattr(ioctx, "foo", attr2, attr2_buf, sizeof(attr2_buf)));
+ rados_xattrs_iter_t iter;
+ ASSERT_EQ(0, rados_getxattrs(ioctx, "foo", &iter));
+ int num_seen = 0;
+ while (true) {
+ const char *name;
+ const char *val;
+ size_t len;
+ ASSERT_EQ(0, rados_getxattrs_next(iter, &name, &val, &len));
+ if (name == NULL) {
+ break;
+ }
+ ASSERT_LT(num_seen, 2);
+ if ((strcmp(name, attr1) == 0) && (val != NULL) && (memcmp(val, attr1_buf, len) == 0)) {
+ num_seen++;
+ continue;
+ }
+ else if ((strcmp(name, attr2) == 0) && (val != NULL) && (memcmp(val, attr2_buf, len) == 0)) {
+ num_seen++;
+ continue;
+ }
+ else {
+ ASSERT_EQ(0, 1);
+ }
+ }
+ rados_getxattrs_end(iter);
+}
+
+TEST_F(LibRadosIoEC, SimpleWrite) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ rados_ioctx_set_namespace(ioctx, "nspace");
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+}
+
+TEST_F(LibRadosIoEC, RoundTrip) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char buf2[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ memset(buf2, 0, sizeof(buf2));
+ ASSERT_EQ((int)sizeof(buf2), rados_read(ioctx, "foo", buf2, sizeof(buf2), 0));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+
+ uint64_t off = 19;
+ ASSERT_EQ(-EOPNOTSUPP, rados_write(ioctx, "bar", buf, sizeof(buf), off));
+}
+
+TEST_F(LibRadosIoEC, OverlappingWriteRoundTrip) {
+ SKIP_IF_CRIMSON();
+ int bsize = alignment;
+ int dbsize = bsize * 2;
+ char *buf = (char *)new char[dbsize];
+ char *buf2 = (char *)new char[bsize];
+ char *buf3 = (char *)new char[dbsize];
+ auto cleanup = [&] {
+ delete[] buf;
+ delete[] buf2;
+ delete[] buf3;
+ };
+ scope_guard<decltype(cleanup)> sg(std::move(cleanup));
+ memset(buf, 0xcc, dbsize);
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, dbsize, 0));
+ memset(buf2, 0xdd, bsize);
+ ASSERT_EQ(-EOPNOTSUPP, rados_write(ioctx, "foo", buf2, bsize, 0));
+ memset(buf3, 0xdd, dbsize);
+ ASSERT_EQ(dbsize, rados_read(ioctx, "foo", buf3, dbsize, 0));
+ // Read the same as first write
+ ASSERT_EQ(0, memcmp(buf3, buf, dbsize));
+}
+
+TEST_F(LibRadosIoEC, WriteFullRoundTrip) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char buf2[64];
+ char buf3[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_write_full(ioctx, "foo", buf2, sizeof(buf2)));
+ memset(buf3, 0xee, sizeof(buf3));
+ ASSERT_EQ((int)sizeof(buf2), rados_read(ioctx, "foo", buf3, sizeof(buf3), 0));
+ ASSERT_EQ(0, memcmp(buf3, buf2, sizeof(buf2)));
+}
+
+TEST_F(LibRadosIoEC, AppendRoundTrip) {
+ SKIP_IF_CRIMSON();
+ char *buf = (char *)new char[alignment];
+ char *buf2 = (char *)new char[alignment];
+ char *buf3 = (char *)new char[alignment *2];
+ int uasize = alignment/2;
+ char *unalignedbuf = (char *)new char[uasize];
+ auto cleanup = [&] {
+ delete[] buf;
+ delete[] buf2;
+ delete[] buf3;
+ delete[] unalignedbuf;
+ };
+ scope_guard<decltype(cleanup)> sg(std::move(cleanup));
+ memset(buf, 0xde, alignment);
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, alignment));
+ memset(buf2, 0xad, alignment);
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf2, alignment));
+ memset(buf3, 0, alignment*2);
+ ASSERT_EQ((int)alignment*2, rados_read(ioctx, "foo", buf3, alignment*2, 0));
+ ASSERT_EQ(0, memcmp(buf3, buf, alignment));
+ ASSERT_EQ(0, memcmp(buf3 + alignment, buf2, alignment));
+ memset(unalignedbuf, 0, uasize);
+ ASSERT_EQ(0, rados_append(ioctx, "foo", unalignedbuf, uasize));
+ ASSERT_EQ(-EOPNOTSUPP, rados_append(ioctx, "foo", unalignedbuf, uasize));
+}
+
+TEST_F(LibRadosIoEC, TruncTest) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char buf2[sizeof(buf)];
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(-EOPNOTSUPP, rados_trunc(ioctx, "foo", sizeof(buf) / 2));
+ memset(buf2, 0, sizeof(buf2));
+ // Same size
+ ASSERT_EQ((int)sizeof(buf), rados_read(ioctx, "foo", buf2, sizeof(buf2), 0));
+ // No change
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+}
+
+TEST_F(LibRadosIoEC, RemoveTest) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char buf2[sizeof(buf)];
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_remove(ioctx, "foo"));
+ memset(buf2, 0, sizeof(buf2));
+ ASSERT_EQ(-ENOENT, rados_read(ioctx, "foo", buf2, sizeof(buf2), 0));
+}
+
+TEST_F(LibRadosIoEC, XattrsRoundTrip) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(-ENODATA, rados_getxattr(ioctx, "foo", attr1, buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_setxattr(ioctx, "foo", attr1, attr1_buf, sizeof(attr1_buf)));
+ ASSERT_EQ((int)sizeof(attr1_buf),
+ rados_getxattr(ioctx, "foo", attr1, buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(attr1_buf, buf, sizeof(attr1_buf)));
+}
+
+TEST_F(LibRadosIoEC, RmXattr) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(0,
+ rados_setxattr(ioctx, "foo", attr1, attr1_buf, sizeof(attr1_buf)));
+ ASSERT_EQ(0, rados_rmxattr(ioctx, "foo", attr1));
+ ASSERT_EQ(-ENODATA, rados_getxattr(ioctx, "foo", attr1, buf, sizeof(buf)));
+
+ // Test rmxattr on a removed object
+ char buf2[128];
+ char attr2[] = "attr2";
+ char attr2_buf[] = "foo bar baz";
+ memset(buf2, 0xbb, sizeof(buf2));
+ ASSERT_EQ(0, rados_write(ioctx, "foo_rmxattr", buf2, sizeof(buf2), 0));
+ ASSERT_EQ(0,
+ rados_setxattr(ioctx, "foo_rmxattr", attr2, attr2_buf, sizeof(attr2_buf)));
+ ASSERT_EQ(0, rados_remove(ioctx, "foo_rmxattr"));
+ ASSERT_EQ(-ENOENT, rados_rmxattr(ioctx, "foo_rmxattr", attr2));
+}
+
+TEST_F(LibRadosIoEC, XattrIter) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ char attr2[] = "attr2";
+ char attr2_buf[256];
+ for (size_t j = 0; j < sizeof(attr2_buf); ++j) {
+ attr2_buf[j] = j % 0xff;
+ }
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_append(ioctx, "foo", buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_setxattr(ioctx, "foo", attr1, attr1_buf, sizeof(attr1_buf)));
+ ASSERT_EQ(0, rados_setxattr(ioctx, "foo", attr2, attr2_buf, sizeof(attr2_buf)));
+ rados_xattrs_iter_t iter;
+ ASSERT_EQ(0, rados_getxattrs(ioctx, "foo", &iter));
+ int num_seen = 0;
+ while (true) {
+ const char *name;
+ const char *val;
+ size_t len;
+ ASSERT_EQ(0, rados_getxattrs_next(iter, &name, &val, &len));
+ if (name == NULL) {
+ break;
+ }
+ ASSERT_LT(num_seen, 2);
+ if ((strcmp(name, attr1) == 0) && (val != NULL) && (memcmp(val, attr1_buf, len) == 0)) {
+ num_seen++;
+ continue;
+ }
+ else if ((strcmp(name, attr2) == 0) && (val != NULL) && (memcmp(val, attr2_buf, len) == 0)) {
+ num_seen++;
+ continue;
+ }
+ else {
+ ASSERT_EQ(0, 1);
+ }
+ }
+ rados_getxattrs_end(iter);
+}
diff --git a/src/test/librados/io_cxx.cc b/src/test/librados/io_cxx.cc
new file mode 100644
index 000000000..d9606d16b
--- /dev/null
+++ b/src/test/librados/io_cxx.cc
@@ -0,0 +1,986 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*
+// vim: ts=8 sw=2 smarttab
+
+#include <climits>
+#include <errno.h>
+
+#include "gtest/gtest.h"
+
+#include "include/rados/librados.hpp"
+#include "include/encoding.h"
+#include "include/err.h"
+#include "include/scope_guard.h"
+#include "test/librados/test_cxx.h"
+#include "test/librados/testcase_cxx.h"
+
+#include "crimson_utils.h"
+
+using namespace librados;
+using std::string;
+
+typedef RadosTestPP LibRadosIoPP;
+typedef RadosTestECPP LibRadosIoECPP;
+
+TEST_F(LibRadosIoPP, TooBigPP) {
+ IoCtx ioctx;
+ bufferlist bl;
+ ASSERT_EQ(-E2BIG, ioctx.write("foo", bl, UINT_MAX, 0));
+ ASSERT_EQ(-E2BIG, ioctx.append("foo", bl, UINT_MAX));
+ // ioctx.write_full no way to overflow bl.length()
+ ASSERT_EQ(-E2BIG, ioctx.writesame("foo", bl, UINT_MAX, 0));
+}
+
+TEST_F(LibRadosIoPP, SimpleWritePP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ ioctx.set_namespace("nspace");
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+}
+
+TEST_F(LibRadosIoPP, ReadOpPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+
+ {
+ bufferlist op_bl;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist op_bl;
+ ObjectReadOperation op;
+ op.read(0, 0, NULL, NULL); //len=0 mean read the whole object data.
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist read_bl, op_bl;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), read_bl.length());
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(read_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist op_bl;
+ int rval = 1000;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), NULL, &rval);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist read_bl, op_bl;
+ int rval = 1000;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl, &rval);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), read_bl.length());
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(read_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist read_bl1, read_bl2, op_bl;
+ int rval1 = 1000, rval2 = 1002;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl1, &rval1);
+ op.read(0, sizeof(buf), &read_bl2, &rval2);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), read_bl1.length());
+ ASSERT_EQ(sizeof(buf), read_bl2.length());
+ ASSERT_EQ(sizeof(buf) * 2, op_bl.length());
+ ASSERT_EQ(0, rval1);
+ ASSERT_EQ(0, rval2);
+ ASSERT_EQ(0, memcmp(read_bl1.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(read_bl2.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(op_bl.c_str() + sizeof(buf), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist op_bl;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist read_bl;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_EQ(sizeof(buf), read_bl.length());
+ ASSERT_EQ(0, memcmp(read_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ int rval = 1000;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), NULL, &rval);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_EQ(0, rval);
+ }
+
+ {
+ bufferlist read_bl;
+ int rval = 1000;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl, &rval);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_EQ(sizeof(buf), read_bl.length());
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(read_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist read_bl1, read_bl2;
+ int rval1 = 1000, rval2 = 1002;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl1, &rval1);
+ op.read(0, sizeof(buf), &read_bl2, &rval2);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_EQ(sizeof(buf), read_bl1.length());
+ ASSERT_EQ(sizeof(buf), read_bl2.length());
+ ASSERT_EQ(0, rval1);
+ ASSERT_EQ(0, rval2);
+ ASSERT_EQ(0, memcmp(read_bl1.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(read_bl2.c_str(), buf, sizeof(buf)));
+ }
+
+ // read into a preallocated buffer with a cached crc
+ {
+ bufferlist op_bl;
+ op_bl.append(std::string(sizeof(buf), 'x'));
+ ASSERT_NE(op_bl.crc32c(0), bl.crc32c(0)); // cache 'x' crc
+
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(op_bl.crc32c(0), bl.crc32c(0));
+ }
+}
+
+TEST_F(LibRadosIoPP, SparseReadOpPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+
+ {
+ std::map<uint64_t, uint64_t> extents;
+ bufferlist read_bl;
+ int rval = -1;
+ ObjectReadOperation op;
+ op.sparse_read(0, sizeof(buf), &extents, &read_bl, &rval);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, nullptr));
+ ASSERT_EQ(0, rval);
+ assert_eq_sparse(bl, extents, read_bl);
+ }
+ {
+ bufferlist bl;
+ bl.append(buf, sizeof(buf) / 2);
+
+ std::map<uint64_t, uint64_t> extents;
+ bufferlist read_bl;
+ int rval = -1;
+ ObjectReadOperation op;
+ op.sparse_read(0, sizeof(buf), &extents, &read_bl, &rval, sizeof(buf) / 2, 1);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, nullptr));
+ ASSERT_EQ(0, rval);
+ assert_eq_sparse(bl, extents, read_bl);
+ }
+}
+
+TEST_F(LibRadosIoPP, RoundTripPP) {
+ char buf[128];
+ Rados cluster;
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ bufferlist cl;
+ ASSERT_EQ((int)sizeof(buf), ioctx.read("foo", cl, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(buf, cl.c_str(), sizeof(buf)));
+}
+
+TEST_F(LibRadosIoPP, RoundTripPP2)
+{
+ bufferlist bl;
+ bl.append("ceph");
+ ObjectWriteOperation write;
+ write.write(0, bl);
+ write.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ASSERT_EQ(0, ioctx.operate("foo", &write));
+
+ ObjectReadOperation read;
+ read.read(0, bl.length(), NULL, NULL);
+ read.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_NOCACHE|LIBRADOS_OP_FLAG_FADVISE_RANDOM);
+ ASSERT_EQ(0, ioctx.operate("foo", &read, &bl));
+ ASSERT_EQ(0, memcmp(bl.c_str(), "ceph", 4));
+}
+
+TEST_F(LibRadosIoPP, Checksum) {
+ char buf[128];
+ Rados cluster;
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ bufferlist init_value_bl;
+ encode(static_cast<uint32_t>(-1), init_value_bl);
+ bufferlist csum_bl;
+ ASSERT_EQ(0, ioctx.checksum("foo", LIBRADOS_CHECKSUM_TYPE_CRC32C,
+ init_value_bl, sizeof(buf), 0, 0, &csum_bl));
+ auto csum_bl_it = csum_bl.cbegin();
+ uint32_t csum_count;
+ decode(csum_count, csum_bl_it);
+ ASSERT_EQ(1U, csum_count);
+ uint32_t csum;
+ decode(csum, csum_bl_it);
+ ASSERT_EQ(bl.crc32c(-1), csum);
+}
+
+TEST_F(LibRadosIoPP, ReadIntoBufferlist) {
+
+ // here we test reading into a non-empty bufferlist referencing existing
+ // buffers
+
+ char buf[128];
+ Rados cluster;
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ bufferlist bl2;
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xbb, sizeof(buf2));
+ bl2.append(buffer::create_static(sizeof(buf2), buf2));
+ ASSERT_EQ((int)sizeof(buf), ioctx.read("foo", bl2, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+}
+
+TEST_F(LibRadosIoPP, OverlappingWriteRoundTripPP) {
+ char buf[128];
+ char buf2[64];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), 0));
+ bufferlist bl3;
+ ASSERT_EQ((int)sizeof(buf), ioctx.read("foo", bl3, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf2, sizeof(buf2)));
+ ASSERT_EQ(0, memcmp(bl3.c_str() + sizeof(buf2), buf, sizeof(buf) - sizeof(buf2)));
+}
+
+TEST_F(LibRadosIoPP, WriteFullRoundTripPP) {
+ char buf[128];
+ char buf2[64];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ASSERT_EQ(0, ioctx.write_full("foo", bl2));
+ bufferlist bl3;
+ ASSERT_EQ((int)sizeof(buf2), ioctx.read("foo", bl3, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf2, sizeof(buf2)));
+}
+
+TEST_F(LibRadosIoPP, WriteFullRoundTripPP2)
+{
+ bufferlist bl;
+ bl.append("ceph");
+ ObjectWriteOperation write;
+ write.write_full(bl);
+ write.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_NOCACHE);
+ ASSERT_EQ(0, ioctx.operate("foo", &write));
+
+ ObjectReadOperation read;
+ read.read(0, bl.length(), NULL, NULL);
+ read.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED|LIBRADOS_OP_FLAG_FADVISE_RANDOM);
+ ASSERT_EQ(0, ioctx.operate("foo", &read, &bl));
+ ASSERT_EQ(0, memcmp(bl.c_str(), "ceph", 4));
+}
+
+TEST_F(LibRadosIoPP, AppendRoundTripPP) {
+ char buf[64];
+ char buf2[64];
+ memset(buf, 0xde, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.append("foo", bl1, sizeof(buf)));
+ memset(buf2, 0xad, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ASSERT_EQ(0, ioctx.append("foo", bl2, sizeof(buf2)));
+ bufferlist bl3;
+ ASSERT_EQ((int)(sizeof(buf) + sizeof(buf2)),
+ ioctx.read("foo", bl3, (sizeof(buf) + sizeof(buf2)), 0));
+ const char *bl3_str = bl3.c_str();
+ ASSERT_EQ(0, memcmp(bl3_str, buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(bl3_str + sizeof(buf), buf2, sizeof(buf2)));
+}
+
+TEST_F(LibRadosIoPP, TruncTestPP) {
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.append("foo", bl, sizeof(buf)));
+ ASSERT_EQ(0, ioctx.trunc("foo", sizeof(buf) / 2));
+ bufferlist bl2;
+ ASSERT_EQ((int)(sizeof(buf)/2), ioctx.read("foo", bl2, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(bl2.c_str(), buf, sizeof(buf)/2));
+}
+
+TEST_F(LibRadosIoPP, RemoveTestPP) {
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.append("foo", bl1, sizeof(buf)));
+ ASSERT_EQ(0, ioctx.remove("foo"));
+ bufferlist bl2;
+ ASSERT_EQ(-ENOENT, ioctx.read("foo", bl2, sizeof(buf), 0));
+}
+
+TEST_F(LibRadosIoPP, XattrsRoundTripPP) {
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.append("foo", bl1, sizeof(buf)));
+ bufferlist bl2;
+ ASSERT_EQ(-ENODATA, ioctx.getxattr("foo", attr1, bl2));
+ bufferlist bl3;
+ bl3.append(attr1_buf, sizeof(attr1_buf));
+ ASSERT_EQ(0, ioctx.setxattr("foo", attr1, bl3));
+ bufferlist bl4;
+ ASSERT_EQ((int)sizeof(attr1_buf),
+ ioctx.getxattr("foo", attr1, bl4));
+ ASSERT_EQ(0, memcmp(bl4.c_str(), attr1_buf, sizeof(attr1_buf)));
+}
+
+TEST_F(LibRadosIoPP, RmXattrPP) {
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.append("foo", bl1, sizeof(buf)));
+ bufferlist bl2;
+ bl2.append(attr1_buf, sizeof(attr1_buf));
+ ASSERT_EQ(0, ioctx.setxattr("foo", attr1, bl2));
+ ASSERT_EQ(0, ioctx.rmxattr("foo", attr1));
+ bufferlist bl3;
+ ASSERT_EQ(-ENODATA, ioctx.getxattr("foo", attr1, bl3));
+
+ // Test rmxattr on a removed object
+ char buf2[128];
+ char attr2[] = "attr2";
+ char attr2_buf[] = "foo bar baz";
+ memset(buf2, 0xbb, sizeof(buf2));
+ bufferlist bl21;
+ bl21.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo_rmxattr", bl21, sizeof(buf2), 0));
+ bufferlist bl22;
+ bl22.append(attr2_buf, sizeof(attr2_buf));
+ ASSERT_EQ(0, ioctx.setxattr("foo_rmxattr", attr2, bl22));
+ ASSERT_EQ(0, ioctx.remove("foo_rmxattr"));
+ ASSERT_EQ(-ENOENT, ioctx.rmxattr("foo_rmxattr", attr2));
+}
+
+TEST_F(LibRadosIoPP, XattrListPP) {
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ char attr2[] = "attr2";
+ char attr2_buf[256];
+ for (size_t j = 0; j < sizeof(attr2_buf); ++j) {
+ attr2_buf[j] = j % 0xff;
+ }
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.append("foo", bl1, sizeof(buf)));
+ bufferlist bl2;
+ bl2.append(attr1_buf, sizeof(attr1_buf));
+ ASSERT_EQ(0, ioctx.setxattr("foo", attr1, bl2));
+ bufferlist bl3;
+ bl3.append(attr2_buf, sizeof(attr2_buf));
+ ASSERT_EQ(0, ioctx.setxattr("foo", attr2, bl3));
+ std::map<std::string, bufferlist> attrset;
+ ASSERT_EQ(0, ioctx.getxattrs("foo", attrset));
+ for (std::map<std::string, bufferlist>::iterator i = attrset.begin();
+ i != attrset.end(); ++i) {
+ if (i->first == string(attr1)) {
+ ASSERT_EQ(0, memcmp(i->second.c_str(), attr1_buf, sizeof(attr1_buf)));
+ }
+ else if (i->first == string(attr2)) {
+ ASSERT_EQ(0, memcmp(i->second.c_str(), attr2_buf, sizeof(attr2_buf)));
+ }
+ else {
+ ASSERT_EQ(0, 1);
+ }
+ }
+}
+
+TEST_F(LibRadosIoECPP, SimpleWritePP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ ioctx.set_namespace("nspace");
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+}
+
+TEST_F(LibRadosIoECPP, ReadOpPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+
+ {
+ bufferlist op_bl;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist op_bl;
+ ObjectReadOperation op;
+ op.read(0, 0, NULL, NULL); //len=0 mean read the whole object data
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist read_bl, op_bl;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), read_bl.length());
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(read_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist op_bl;
+ int rval = 1000;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), NULL, &rval);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist read_bl, op_bl;
+ int rval = 1000;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl, &rval);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), read_bl.length());
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(read_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist read_bl1, read_bl2, op_bl;
+ int rval1 = 1000, rval2 = 1002;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl1, &rval1);
+ op.read(0, sizeof(buf), &read_bl2, &rval2);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), read_bl1.length());
+ ASSERT_EQ(sizeof(buf), read_bl2.length());
+ ASSERT_EQ(sizeof(buf) * 2, op_bl.length());
+ ASSERT_EQ(0, rval1);
+ ASSERT_EQ(0, rval2);
+ ASSERT_EQ(0, memcmp(read_bl1.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(read_bl2.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(op_bl.c_str() + sizeof(buf), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist op_bl;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist read_bl;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_EQ(sizeof(buf), read_bl.length());
+ ASSERT_EQ(0, memcmp(read_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ int rval = 1000;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), NULL, &rval);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_EQ(0, rval);
+ }
+
+ {
+ bufferlist read_bl;
+ int rval = 1000;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl, &rval);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_EQ(sizeof(buf), read_bl.length());
+ ASSERT_EQ(0, rval);
+ ASSERT_EQ(0, memcmp(read_bl.c_str(), buf, sizeof(buf)));
+ }
+
+ {
+ bufferlist read_bl1, read_bl2;
+ int rval1 = 1000, rval2 = 1002;
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), &read_bl1, &rval1);
+ op.read(0, sizeof(buf), &read_bl2, &rval2);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_EQ(sizeof(buf), read_bl1.length());
+ ASSERT_EQ(sizeof(buf), read_bl2.length());
+ ASSERT_EQ(0, rval1);
+ ASSERT_EQ(0, rval2);
+ ASSERT_EQ(0, memcmp(read_bl1.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(read_bl2.c_str(), buf, sizeof(buf)));
+ }
+
+ // read into a preallocated buffer with a cached crc
+ {
+ bufferlist op_bl;
+ op_bl.append(std::string(sizeof(buf), 'x'));
+ ASSERT_NE(op_bl.crc32c(0), bl.crc32c(0)); // cache 'x' crc
+
+ ObjectReadOperation op;
+ op.read(0, sizeof(buf), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, &op_bl));
+
+ ASSERT_EQ(sizeof(buf), op_bl.length());
+ ASSERT_EQ(0, memcmp(op_bl.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(op_bl.crc32c(0), bl.crc32c(0));
+ }
+}
+
+TEST_F(LibRadosIoECPP, SparseReadOpPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+
+ {
+ std::map<uint64_t, uint64_t> extents;
+ bufferlist read_bl;
+ int rval = -1;
+ ObjectReadOperation op;
+ op.sparse_read(0, sizeof(buf), &extents, &read_bl, &rval);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, nullptr));
+ ASSERT_EQ(0, rval);
+ assert_eq_sparse(bl, extents, read_bl);
+ }
+}
+
+TEST_F(LibRadosIoECPP, RoundTripPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ Rados cluster;
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ bufferlist cl;
+ ASSERT_EQ((int)sizeof(buf), ioctx.read("foo", cl, sizeof(buf) * 3, 0));
+ ASSERT_EQ(0, memcmp(buf, cl.c_str(), sizeof(buf)));
+}
+
+TEST_F(LibRadosIoECPP, RoundTripPP2)
+{
+ SKIP_IF_CRIMSON();
+ bufferlist bl;
+ bl.append("ceph");
+ ObjectWriteOperation write;
+ write.write(0, bl);
+ write.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ASSERT_EQ(0, ioctx.operate("foo", &write));
+
+ ObjectReadOperation read;
+ read.read(0, bl.length(), NULL, NULL);
+ read.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED|LIBRADOS_OP_FLAG_FADVISE_RANDOM);
+ ASSERT_EQ(0, ioctx.operate("foo", &read, &bl));
+ ASSERT_EQ(0, memcmp(bl.c_str(), "ceph", 4));
+}
+
+TEST_F(LibRadosIoECPP, OverlappingWriteRoundTripPP) {
+ SKIP_IF_CRIMSON();
+ int bsize = alignment;
+ int dbsize = bsize * 2;
+ char *buf = (char *)new char[dbsize];
+ char *buf2 = (char *)new char[bsize];
+ auto cleanup = [&] {
+ delete[] buf;
+ delete[] buf2;
+ };
+ scope_guard<decltype(cleanup)> sg(std::move(cleanup));
+ memset(buf, 0xcc, dbsize);
+ bufferlist bl1;
+ bl1.append(buf, dbsize);
+ ASSERT_EQ(0, ioctx.write("foo", bl1, dbsize, 0));
+ memset(buf2, 0xdd, bsize);
+ bufferlist bl2;
+ bl2.append(buf2, bsize);
+ ASSERT_EQ(-EOPNOTSUPP, ioctx.write("foo", bl2, bsize, 0));
+ bufferlist bl3;
+ ASSERT_EQ(dbsize, ioctx.read("foo", bl3, dbsize, 0));
+ // Read the same as first write
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, dbsize));
+}
+
+TEST_F(LibRadosIoECPP, WriteFullRoundTripPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char buf2[64];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ASSERT_EQ(0, ioctx.write_full("foo", bl2));
+ bufferlist bl3;
+ ASSERT_EQ((int)sizeof(buf2), ioctx.read("foo", bl3, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf2, sizeof(buf2)));
+}
+
+TEST_F(LibRadosIoECPP, WriteFullRoundTripPP2)
+{
+ SKIP_IF_CRIMSON();
+ bufferlist bl;
+ bl.append("ceph");
+ ObjectWriteOperation write;
+ write.write_full(bl);
+ write.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ASSERT_EQ(0, ioctx.operate("foo", &write));
+
+ ObjectReadOperation read;
+ read.read(0, bl.length(), NULL, NULL);
+ read.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_DONTNEED|LIBRADOS_OP_FLAG_FADVISE_RANDOM);
+ ASSERT_EQ(0, ioctx.operate("foo", &read, &bl));
+ ASSERT_EQ(0, memcmp(bl.c_str(), "ceph", 4));
+}
+
+TEST_F(LibRadosIoECPP, AppendRoundTripPP) {
+ SKIP_IF_CRIMSON();
+ char *buf = (char *)new char[alignment];
+ char *buf2 = (char *)new char[alignment];
+ auto cleanup = [&] {
+ delete[] buf;
+ delete[] buf2;
+ };
+ scope_guard<decltype(cleanup)> sg(std::move(cleanup));
+ memset(buf, 0xde, alignment);
+ bufferlist bl1;
+ bl1.append(buf, alignment);
+ ASSERT_EQ(0, ioctx.append("foo", bl1, alignment));
+ memset(buf2, 0xad, alignment);
+ bufferlist bl2;
+ bl2.append(buf2, alignment);
+ ASSERT_EQ(0, ioctx.append("foo", bl2, alignment));
+ bufferlist bl3;
+ ASSERT_EQ((int)(alignment * 2),
+ ioctx.read("foo", bl3, (alignment * 4), 0));
+ const char *bl3_str = bl3.c_str();
+ ASSERT_EQ(0, memcmp(bl3_str, buf, alignment));
+ ASSERT_EQ(0, memcmp(bl3_str + alignment, buf2, alignment));
+}
+
+TEST_F(LibRadosIoECPP, TruncTestPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.append("foo", bl, sizeof(buf)));
+ ASSERT_EQ(-EOPNOTSUPP, ioctx.trunc("foo", sizeof(buf) / 2));
+ bufferlist bl2;
+ // Same size
+ ASSERT_EQ((int)sizeof(buf), ioctx.read("foo", bl2, sizeof(buf), 0));
+ // No change
+ ASSERT_EQ(0, memcmp(bl2.c_str(), buf, sizeof(buf)));
+}
+
+TEST_F(LibRadosIoECPP, RemoveTestPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.append("foo", bl1, sizeof(buf)));
+ ASSERT_EQ(0, ioctx.remove("foo"));
+ bufferlist bl2;
+ ASSERT_EQ(-ENOENT, ioctx.read("foo", bl2, sizeof(buf), 0));
+}
+
+TEST_F(LibRadosIoECPP, XattrsRoundTripPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.append("foo", bl1, sizeof(buf)));
+ bufferlist bl2;
+ ASSERT_EQ(-ENODATA, ioctx.getxattr("foo", attr1, bl2));
+ bufferlist bl3;
+ bl3.append(attr1_buf, sizeof(attr1_buf));
+ ASSERT_EQ(0, ioctx.setxattr("foo", attr1, bl3));
+ bufferlist bl4;
+ ASSERT_EQ((int)sizeof(attr1_buf),
+ ioctx.getxattr("foo", attr1, bl4));
+ ASSERT_EQ(0, memcmp(bl4.c_str(), attr1_buf, sizeof(attr1_buf)));
+}
+
+TEST_F(LibRadosIoECPP, RmXattrPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.append("foo", bl1, sizeof(buf)));
+ bufferlist bl2;
+ bl2.append(attr1_buf, sizeof(attr1_buf));
+ ASSERT_EQ(0, ioctx.setxattr("foo", attr1, bl2));
+ ASSERT_EQ(0, ioctx.rmxattr("foo", attr1));
+ bufferlist bl3;
+ ASSERT_EQ(-ENODATA, ioctx.getxattr("foo", attr1, bl3));
+
+ // Test rmxattr on a removed object
+ char buf2[128];
+ char attr2[] = "attr2";
+ char attr2_buf[] = "foo bar baz";
+ memset(buf2, 0xbb, sizeof(buf2));
+ bufferlist bl21;
+ bl21.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo_rmxattr", bl21, sizeof(buf2), 0));
+ bufferlist bl22;
+ bl22.append(attr2_buf, sizeof(attr2_buf));
+ ASSERT_EQ(0, ioctx.setxattr("foo_rmxattr", attr2, bl22));
+ ASSERT_EQ(0, ioctx.remove("foo_rmxattr"));
+ ASSERT_EQ(-ENOENT, ioctx.rmxattr("foo_rmxattr", attr2));
+}
+
+TEST_F(LibRadosIoECPP, XattrListPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char attr1[] = "attr1";
+ char attr1_buf[] = "foo bar baz";
+ char attr2[] = "attr2";
+ char attr2_buf[256];
+ for (size_t j = 0; j < sizeof(attr2_buf); ++j) {
+ attr2_buf[j] = j % 0xff;
+ }
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.append("foo", bl1, sizeof(buf)));
+ bufferlist bl2;
+ bl2.append(attr1_buf, sizeof(attr1_buf));
+ ASSERT_EQ(0, ioctx.setxattr("foo", attr1, bl2));
+ bufferlist bl3;
+ bl3.append(attr2_buf, sizeof(attr2_buf));
+ ASSERT_EQ(0, ioctx.setxattr("foo", attr2, bl3));
+ std::map<std::string, bufferlist> attrset;
+ ASSERT_EQ(0, ioctx.getxattrs("foo", attrset));
+ for (std::map<std::string, bufferlist>::iterator i = attrset.begin();
+ i != attrset.end(); ++i) {
+ if (i->first == string(attr1)) {
+ ASSERT_EQ(0, memcmp(i->second.c_str(), attr1_buf, sizeof(attr1_buf)));
+ }
+ else if (i->first == string(attr2)) {
+ ASSERT_EQ(0, memcmp(i->second.c_str(), attr2_buf, sizeof(attr2_buf)));
+ }
+ else {
+ ASSERT_EQ(0, 1);
+ }
+ }
+}
+
+TEST_F(LibRadosIoPP, CmpExtPP) {
+ bufferlist bl;
+ bl.append("ceph");
+ ObjectWriteOperation write1;
+ write1.write(0, bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &write1));
+
+ bufferlist new_bl;
+ new_bl.append("CEPH");
+ ObjectWriteOperation write2;
+ write2.cmpext(0, bl, nullptr);
+ write2.write(0, new_bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &write2));
+
+ ObjectReadOperation read;
+ read.read(0, bl.length(), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &read, &bl));
+ ASSERT_EQ(0, memcmp(bl.c_str(), "CEPH", 4));
+}
+
+TEST_F(LibRadosIoPP, CmpExtDNEPP) {
+ bufferlist bl;
+ bl.append(std::string(4, '\0'));
+
+ bufferlist new_bl;
+ new_bl.append("CEPH");
+ ObjectWriteOperation write;
+ write.cmpext(0, bl, nullptr);
+ write.write(0, new_bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &write));
+
+ ObjectReadOperation read;
+ read.read(0, bl.length(), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &read, &bl));
+ ASSERT_EQ(0, memcmp(bl.c_str(), "CEPH", 4));
+}
+
+TEST_F(LibRadosIoPP, CmpExtMismatchPP) {
+ bufferlist bl;
+ bl.append("ceph");
+ ObjectWriteOperation write1;
+ write1.write(0, bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &write1));
+
+ bufferlist new_bl;
+ new_bl.append("CEPH");
+ ObjectWriteOperation write2;
+ write2.cmpext(0, new_bl, nullptr);
+ write2.write(0, new_bl);
+ ASSERT_EQ(-MAX_ERRNO, ioctx.operate("foo", &write2));
+
+ ObjectReadOperation read;
+ read.read(0, bl.length(), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &read, &bl));
+ ASSERT_EQ(0, memcmp(bl.c_str(), "ceph", 4));
+}
+
+TEST_F(LibRadosIoECPP, CmpExtPP) {
+ SKIP_IF_CRIMSON();
+ bufferlist bl;
+ bl.append("ceph");
+ ObjectWriteOperation write1;
+ write1.write(0, bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &write1));
+
+ bufferlist new_bl;
+ new_bl.append("CEPH");
+ ObjectWriteOperation write2;
+ write2.cmpext(0, bl, nullptr);
+ write2.write_full(new_bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &write2));
+
+ ObjectReadOperation read;
+ read.read(0, bl.length(), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &read, &bl));
+ ASSERT_EQ(0, memcmp(bl.c_str(), "CEPH", 4));
+}
+
+TEST_F(LibRadosIoECPP, CmpExtDNEPP) {
+ SKIP_IF_CRIMSON();
+ bufferlist bl;
+ bl.append(std::string(4, '\0'));
+
+ bufferlist new_bl;
+ new_bl.append("CEPH");
+ ObjectWriteOperation write;
+ write.cmpext(0, bl, nullptr);
+ write.write_full(new_bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &write));
+
+ ObjectReadOperation read;
+ read.read(0, bl.length(), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &read, &bl));
+ ASSERT_EQ(0, memcmp(bl.c_str(), "CEPH", 4));
+}
+
+TEST_F(LibRadosIoECPP, CmpExtMismatchPP) {
+ SKIP_IF_CRIMSON();
+ bufferlist bl;
+ bl.append("ceph");
+ ObjectWriteOperation write1;
+ write1.write(0, bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &write1));
+
+ bufferlist new_bl;
+ new_bl.append("CEPH");
+ ObjectWriteOperation write2;
+ write2.cmpext(0, new_bl, nullptr);
+ write2.write_full(new_bl);
+ ASSERT_EQ(-MAX_ERRNO, ioctx.operate("foo", &write2));
+
+ ObjectReadOperation read;
+ read.read(0, bl.length(), NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &read, &bl));
+ ASSERT_EQ(0, memcmp(bl.c_str(), "ceph", 4));
+}
diff --git a/src/test/librados/librados.cc b/src/test/librados/librados.cc
new file mode 100644
index 000000000..c688724da
--- /dev/null
+++ b/src/test/librados/librados.cc
@@ -0,0 +1,13 @@
+//#include "common/config.h"
+#include "include/rados/librados.h"
+
+#include "gtest/gtest.h"
+
+TEST(Librados, CreateShutdown) {
+ rados_t cluster;
+ int err;
+ err = rados_create(&cluster, "someid");
+ EXPECT_EQ(err, 0);
+
+ rados_shutdown(cluster);
+}
diff --git a/src/test/librados/librados_config.cc b/src/test/librados/librados_config.cc
new file mode 100644
index 000000000..d30fb30ef
--- /dev/null
+++ b/src/test/librados/librados_config.cc
@@ -0,0 +1,98 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2011 New Dream Network
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#include "gtest/gtest.h"
+#include "include/rados/librados.h"
+
+#include <sstream>
+#include <string>
+#include <string.h>
+#include <errno.h>
+
+using std::string;
+
+TEST(LibRadosConfig, SimpleSet) {
+ rados_t cl;
+ int ret = rados_create(&cl, NULL);
+ ASSERT_EQ(ret, 0);
+
+ ret = rados_conf_set(cl, "leveldb_max_open_files", "21");
+ ASSERT_EQ(ret, 0);
+
+ char buf[128];
+ memset(buf, 0, sizeof(buf));
+ ret = rados_conf_get(cl, "leveldb_max_open_files", buf, sizeof(buf));
+ ASSERT_EQ(ret, 0);
+ ASSERT_EQ(string("21"), string(buf));
+
+ rados_shutdown(cl);
+}
+
+TEST(LibRadosConfig, ArgV) {
+ rados_t cl;
+ int ret = rados_create(&cl, NULL);
+ ASSERT_EQ(ret, 0);
+
+ const char *argv[] = { "foo", "--leveldb-max-open-files", "2",
+ "--key", "my-key", NULL };
+ size_t argc = (sizeof(argv) / sizeof(argv[0])) - 1;
+ rados_conf_parse_argv(cl, argc, argv);
+
+ char buf[128];
+ memset(buf, 0, sizeof(buf));
+ ret = rados_conf_get(cl, "key", buf, sizeof(buf));
+ ASSERT_EQ(ret, 0);
+ ASSERT_EQ(string("my-key"), string(buf));
+
+ memset(buf, 0, sizeof(buf));
+ ret = rados_conf_get(cl, "leveldb_max_open_files", buf, sizeof(buf));
+ ASSERT_EQ(ret, 0);
+ ASSERT_EQ(string("2"), string(buf));
+
+ rados_shutdown(cl);
+}
+
+TEST(LibRadosConfig, DebugLevels) {
+ rados_t cl;
+ int ret = rados_create(&cl, NULL);
+ ASSERT_EQ(ret, 0);
+
+ ret = rados_conf_set(cl, "debug_rados", "3");
+ ASSERT_EQ(ret, 0);
+
+ char buf[128];
+ memset(buf, 0, sizeof(buf));
+ ret = rados_conf_get(cl, "debug_rados", buf, sizeof(buf));
+ ASSERT_EQ(ret, 0);
+ ASSERT_EQ(0, strcmp("3/3", buf));
+
+ ret = rados_conf_set(cl, "debug_rados", "7/8");
+ ASSERT_EQ(ret, 0);
+
+ memset(buf, 0, sizeof(buf));
+ ret = rados_conf_get(cl, "debug_rados", buf, sizeof(buf));
+ ASSERT_EQ(ret, 0);
+ ASSERT_EQ(0, strcmp("7/8", buf));
+
+ ret = rados_conf_set(cl, "debug_rados", "foo");
+ ASSERT_EQ(ret, -EINVAL);
+
+ ret = rados_conf_set(cl, "debug_asdkfasdjfajksdf", "foo");
+ ASSERT_EQ(ret, -ENOENT);
+
+ ret = rados_conf_get(cl, "debug_radfjadfsdados", buf, sizeof(buf));
+ ASSERT_EQ(ret, -ENOENT);
+
+ rados_shutdown(cl);
+}
diff --git a/src/test/librados/list.cc b/src/test/librados/list.cc
new file mode 100644
index 000000000..ef3488c0b
--- /dev/null
+++ b/src/test/librados/list.cc
@@ -0,0 +1,555 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#include "include/rados/librados.h"
+#include "include/rados/librados.hpp"
+#include "include/stringify.h"
+#include "test/librados/test.h"
+#include "test/librados/test_common.h"
+#include "test/librados/TestCase.h"
+#include "global/global_context.h"
+
+#include "include/types.h"
+#include "common/hobject.h"
+#include "gtest/gtest.h"
+#include <errno.h>
+#include <string>
+#include <stdexcept>
+
+#include "crimson_utils.h"
+
+using namespace std;
+using namespace librados;
+
+typedef RadosTestNSCleanup LibRadosList;
+typedef RadosTestECNSCleanup LibRadosListEC;
+typedef RadosTestNP LibRadosListNP;
+
+
+TEST_F(LibRadosList, ListObjects) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ rados_list_ctx_t ctx;
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ const char *entry;
+ bool foundit = false;
+ while (rados_nobjects_list_next(ctx, &entry, NULL, NULL) != -ENOENT) {
+ foundit = true;
+ ASSERT_EQ(std::string(entry), "foo");
+ }
+ ASSERT_TRUE(foundit);
+ rados_nobjects_list_close(ctx);
+}
+
+TEST_F(LibRadosList, ListObjectsZeroInName) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo\0bar", buf, sizeof(buf), 0));
+ rados_list_ctx_t ctx;
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ const char *entry;
+ size_t entry_size;
+ bool foundit = false;
+ while (rados_nobjects_list_next2(ctx, &entry, NULL, NULL,
+ &entry_size, NULL, NULL) != -ENOENT) {
+ foundit = true;
+ ASSERT_EQ(std::string(entry, entry_size), "foo\0bar");
+ }
+ ASSERT_TRUE(foundit);
+ rados_nobjects_list_close(ctx);
+}
+
+static void check_list(
+ std::set<std::string>& myset,
+ rados_list_ctx_t& ctx,
+ const std::string &check_nspace)
+{
+ const char *entry, *nspace;
+ cout << "myset " << myset << std::endl;
+ // we should see every item exactly once.
+ int ret;
+ while ((ret = rados_nobjects_list_next(ctx, &entry, NULL, &nspace)) == 0) {
+ std::string test_name;
+ if (check_nspace == all_nspaces) {
+ test_name = std::string(nspace) + ":" + std::string(entry);
+ } else {
+ ASSERT_TRUE(std::string(nspace) == check_nspace);
+ test_name = std::string(entry);
+ }
+ cout << test_name << std::endl;
+
+ ASSERT_TRUE(myset.end() != myset.find(test_name));
+ myset.erase(test_name);
+ }
+ ASSERT_EQ(-ENOENT, ret);
+ ASSERT_TRUE(myset.empty());
+}
+
+TEST_F(LibRadosList, ListObjectsNS) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ // Create :foo1, :foo2, :foo3, n1:foo1, ns1:foo4, ns1:foo5, ns2:foo6, n2:foo7
+ rados_ioctx_set_namespace(ioctx, "");
+ ASSERT_EQ(0, rados_write(ioctx, "foo1", buf, sizeof(buf), 0));
+ rados_ioctx_set_namespace(ioctx, "ns1");
+ ASSERT_EQ(0, rados_write(ioctx, "foo1", buf, sizeof(buf), 0));
+ rados_ioctx_set_namespace(ioctx, "");
+ ASSERT_EQ(0, rados_write(ioctx, "foo2", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_write(ioctx, "foo3", buf, sizeof(buf), 0));
+ rados_ioctx_set_namespace(ioctx, "ns1");
+ ASSERT_EQ(0, rados_write(ioctx, "foo4", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_write(ioctx, "foo5", buf, sizeof(buf), 0));
+ rados_ioctx_set_namespace(ioctx, "ns2");
+ ASSERT_EQ(0, rados_write(ioctx, "foo6", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_write(ioctx, "foo7", buf, sizeof(buf), 0));
+
+ char nspace[4];
+ ASSERT_EQ(-ERANGE, rados_ioctx_get_namespace(ioctx, nspace, 3));
+ ASSERT_EQ(static_cast<int>(strlen("ns2")),
+ rados_ioctx_get_namespace(ioctx, nspace, sizeof(nspace)));
+ ASSERT_EQ(0, strcmp("ns2", nspace));
+
+ std::set<std::string> def, ns1, ns2, all;
+ def.insert(std::string("foo1"));
+ def.insert(std::string("foo2"));
+ def.insert(std::string("foo3"));
+ ns1.insert(std::string("foo1"));
+ ns1.insert(std::string("foo4"));
+ ns1.insert(std::string("foo5"));
+ ns2.insert(std::string("foo6"));
+ ns2.insert(std::string("foo7"));
+ all.insert(std::string(":foo1"));
+ all.insert(std::string(":foo2"));
+ all.insert(std::string(":foo3"));
+ all.insert(std::string("ns1:foo1"));
+ all.insert(std::string("ns1:foo4"));
+ all.insert(std::string("ns1:foo5"));
+ all.insert(std::string("ns2:foo6"));
+ all.insert(std::string("ns2:foo7"));
+
+ rados_list_ctx_t ctx;
+ // Check default namespace ""
+ rados_ioctx_set_namespace(ioctx, "");
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ check_list(def, ctx, "");
+ rados_nobjects_list_close(ctx);
+
+ // Check namespace "ns1"
+ rados_ioctx_set_namespace(ioctx, "ns1");
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ check_list(ns1, ctx, "ns1");
+ rados_nobjects_list_close(ctx);
+
+ // Check namespace "ns2"
+ rados_ioctx_set_namespace(ioctx, "ns2");
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ check_list(ns2, ctx, "ns2");
+ rados_nobjects_list_close(ctx);
+
+ // Check ALL namespaces
+ rados_ioctx_set_namespace(ioctx, LIBRADOS_ALL_NSPACES);
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ check_list(all, ctx, all_nspaces);
+ rados_nobjects_list_close(ctx);
+}
+
+
+TEST_F(LibRadosList, ListObjectsStart) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+
+ for (int i=0; i<16; ++i) {
+ string n = stringify(i);
+ ASSERT_EQ(0, rados_write(ioctx, n.c_str(), buf, sizeof(buf), 0));
+ }
+
+ rados_list_ctx_t ctx;
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ std::map<int, std::set<std::string> > pg_to_obj;
+ const char *entry;
+ while (rados_nobjects_list_next(ctx, &entry, NULL, NULL) == 0) {
+ uint32_t pos = rados_nobjects_list_get_pg_hash_position(ctx);
+ std::cout << entry << " " << pos << std::endl;
+ pg_to_obj[pos].insert(entry);
+ }
+ rados_nobjects_list_close(ctx);
+
+ std::map<int, std::set<std::string> >::reverse_iterator p =
+ pg_to_obj.rbegin();
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ while (p != pg_to_obj.rend()) {
+ ASSERT_EQ((uint32_t)p->first, rados_nobjects_list_seek(ctx, p->first));
+ ASSERT_EQ(0, rados_nobjects_list_next(ctx, &entry, NULL, NULL));
+ std::cout << "have " << entry << " expect one of " << p->second << std::endl;
+ ASSERT_TRUE(p->second.count(entry));
+ ++p;
+ }
+ rados_nobjects_list_close(ctx);
+}
+
+// this function replicates
+// librados::operator<<(std::ostream& os, const librados::ObjectCursor& oc)
+// because we don't want to use librados in librados client.
+std::ostream& operator<<(std::ostream&os, const rados_object_list_cursor& oc)
+{
+ if (oc) {
+ os << *(hobject_t *)oc;
+ } else {
+ os << hobject_t{};
+ }
+ return os;
+}
+
+TEST_F(LibRadosList, ListObjectsCursor) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+
+ const int max_objs = 16;
+
+ for (int i=0; i<max_objs; ++i) {
+ string n = stringify(i);
+ ASSERT_EQ(0, rados_write(ioctx, n.c_str(), buf, sizeof(buf), 0));
+ }
+
+ {
+ rados_list_ctx_t ctx;
+ const char *entry;
+ rados_object_list_cursor cursor;
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ ASSERT_EQ(rados_nobjects_list_get_cursor(ctx, &cursor), 0);
+ rados_object_list_cursor first_cursor = cursor;
+ cout << "x cursor=" << cursor << std::endl;
+ while (rados_nobjects_list_next(ctx, &entry, NULL, NULL) == 0) {
+ string oid = entry;
+ ASSERT_EQ(rados_nobjects_list_get_cursor(ctx, &cursor), 0);
+ cout << "> oid=" << oid << " cursor=" << cursor << std::endl;
+ }
+ rados_nobjects_list_seek_cursor(ctx, first_cursor);
+ ASSERT_EQ(rados_nobjects_list_next(ctx, &entry, NULL, NULL), 0);
+ cout << "FIRST> seek to " << first_cursor << " oid=" << string(entry) << std::endl;
+ }
+ rados_list_ctx_t ctx;
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+
+ std::map<rados_object_list_cursor, string> cursor_to_obj;
+ int count = 0;
+
+ const char *entry;
+ while (rados_nobjects_list_next(ctx, &entry, NULL, NULL) == 0) {
+ rados_object_list_cursor cursor;
+ ASSERT_EQ(rados_nobjects_list_get_cursor(ctx, &cursor), 0);
+ string oid = entry;
+ cout << ": oid=" << oid << " cursor=" << cursor << std::endl;
+ cursor_to_obj[cursor] = oid;
+
+ rados_nobjects_list_seek_cursor(ctx, cursor);
+ cout << ": seek to " << cursor << std::endl;
+ ASSERT_EQ(rados_nobjects_list_next(ctx, &entry, NULL, NULL), 0);
+ cout << "> " << cursor << " -> " << entry << std::endl;
+ ASSERT_EQ(string(entry), oid);
+ ASSERT_LT(count, max_objs); /* avoid infinite loops due to bad seek */
+
+ ++count;
+ }
+
+ ASSERT_EQ(count, max_objs);
+
+ auto p = cursor_to_obj.rbegin();
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ while (p != cursor_to_obj.rend()) {
+ cout << ": seek to " << p->first << std::endl;
+ rados_object_list_cursor cursor;
+ rados_object_list_cursor oid(p->first);
+ rados_nobjects_list_seek_cursor(ctx, oid);
+ ASSERT_EQ(rados_nobjects_list_get_cursor(ctx, &cursor), 0);
+ cout << ": cursor()=" << cursor << " expected=" << oid << std::endl;
+ // ASSERT_EQ(ObjectCursor(oid), ObjectCursor(cursor));
+ ASSERT_EQ(rados_nobjects_list_next(ctx, &entry, NULL, NULL), 0);
+ cout << "> " << cursor << " -> " << entry << std::endl;
+ cout << ": entry=" << entry << " expected=" << p->second << std::endl;
+ ASSERT_EQ(p->second, string(entry));
+
+ ++p;
+
+ rados_object_list_cursor_free(ctx, cursor);
+ }
+}
+
+TEST_F(LibRadosListEC, ListObjects) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ rados_list_ctx_t ctx;
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ const char *entry;
+ bool foundit = false;
+ while (rados_nobjects_list_next(ctx, &entry, NULL, NULL) != -ENOENT) {
+ foundit = true;
+ ASSERT_EQ(std::string(entry), "foo");
+ }
+ ASSERT_TRUE(foundit);
+ rados_nobjects_list_close(ctx);
+}
+
+TEST_F(LibRadosListEC, ListObjectsNS) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ // Create :foo1, :foo2, :foo3, n1:foo1, ns1:foo4, ns1:foo5, ns2:foo6, n2:foo7
+ rados_ioctx_set_namespace(ioctx, "");
+ ASSERT_EQ(0, rados_write(ioctx, "foo1", buf, sizeof(buf), 0));
+ rados_ioctx_set_namespace(ioctx, "ns1");
+ ASSERT_EQ(0, rados_write(ioctx, "foo1", buf, sizeof(buf), 0));
+ rados_ioctx_set_namespace(ioctx, "");
+ ASSERT_EQ(0, rados_write(ioctx, "foo2", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_write(ioctx, "foo3", buf, sizeof(buf), 0));
+ rados_ioctx_set_namespace(ioctx, "ns1");
+ ASSERT_EQ(0, rados_write(ioctx, "foo4", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_write(ioctx, "foo5", buf, sizeof(buf), 0));
+ rados_ioctx_set_namespace(ioctx, "ns2");
+ ASSERT_EQ(0, rados_write(ioctx, "foo6", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_write(ioctx, "foo7", buf, sizeof(buf), 0));
+
+ std::set<std::string> def, ns1, ns2, all;
+ def.insert(std::string("foo1"));
+ def.insert(std::string("foo2"));
+ def.insert(std::string("foo3"));
+ ns1.insert(std::string("foo1"));
+ ns1.insert(std::string("foo4"));
+ ns1.insert(std::string("foo5"));
+ ns2.insert(std::string("foo6"));
+ ns2.insert(std::string("foo7"));
+ all.insert(std::string(":foo1"));
+ all.insert(std::string(":foo2"));
+ all.insert(std::string(":foo3"));
+ all.insert(std::string("ns1:foo1"));
+ all.insert(std::string("ns1:foo4"));
+ all.insert(std::string("ns1:foo5"));
+ all.insert(std::string("ns2:foo6"));
+ all.insert(std::string("ns2:foo7"));
+
+ rados_list_ctx_t ctx;
+ // Check default namespace ""
+ rados_ioctx_set_namespace(ioctx, "");
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ check_list(def, ctx, "");
+ rados_nobjects_list_close(ctx);
+
+ // Check default namespace "ns1"
+ rados_ioctx_set_namespace(ioctx, "ns1");
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ check_list(ns1, ctx, "ns1");
+ rados_nobjects_list_close(ctx);
+
+ // Check default namespace "ns2"
+ rados_ioctx_set_namespace(ioctx, "ns2");
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ check_list(ns2, ctx, "ns2");
+ rados_nobjects_list_close(ctx);
+
+ // Check all namespaces
+ rados_ioctx_set_namespace(ioctx, LIBRADOS_ALL_NSPACES);
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ check_list(all, ctx, all_nspaces);
+ rados_nobjects_list_close(ctx);
+}
+
+
+TEST_F(LibRadosListEC, ListObjectsStart) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+
+ for (int i=0; i<16; ++i) {
+ string n = stringify(i);
+ ASSERT_EQ(0, rados_write(ioctx, n.c_str(), buf, sizeof(buf), 0));
+ }
+
+ rados_list_ctx_t ctx;
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ std::map<int, std::set<std::string> > pg_to_obj;
+ const char *entry;
+ while (rados_nobjects_list_next(ctx, &entry, NULL, NULL) == 0) {
+ uint32_t pos = rados_nobjects_list_get_pg_hash_position(ctx);
+ std::cout << entry << " " << pos << std::endl;
+ pg_to_obj[pos].insert(entry);
+ }
+ rados_nobjects_list_close(ctx);
+
+ std::map<int, std::set<std::string> >::reverse_iterator p =
+ pg_to_obj.rbegin();
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ while (p != pg_to_obj.rend()) {
+ ASSERT_EQ((uint32_t)p->first, rados_nobjects_list_seek(ctx, p->first));
+ ASSERT_EQ(0, rados_nobjects_list_next(ctx, &entry, NULL, NULL));
+ std::cout << "have " << entry << " expect one of " << p->second << std::endl;
+ ASSERT_TRUE(p->second.count(entry));
+ ++p;
+ }
+ rados_nobjects_list_close(ctx);
+}
+
+TEST_F(LibRadosListNP, ListObjectsError) {
+ std::string pool_name;
+ rados_t cluster;
+ rados_ioctx_t ioctx;
+ pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ ASSERT_EQ(0, rados_ioctx_create(cluster, pool_name.c_str(), &ioctx));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ rados_ioctx_set_namespace(ioctx, "");
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+
+ //ASSERT_EQ(0, rados_pool_delete(cluster, pool_name.c_str()));
+ {
+ char *buf, *st;
+ size_t buflen, stlen;
+ string c = "{\"prefix\":\"osd pool rm\",\"pool\": \"" + pool_name +
+ "\",\"pool2\":\"" + pool_name +
+ "\",\"yes_i_really_really_mean_it_not_faking\": true}";
+ const char *cmd[2] = { c.c_str(), 0 };
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, &buf, &buflen, &st, &stlen));
+ ASSERT_EQ(0, rados_wait_for_latest_osdmap(cluster));
+ }
+
+ rados_list_ctx_t ctx;
+ ASSERT_EQ(0, rados_nobjects_list_open(ioctx, &ctx));
+ const char *entry;
+ ASSERT_EQ(-ENOENT, rados_nobjects_list_next(ctx, &entry, NULL, NULL));
+ rados_nobjects_list_close(ctx);
+ rados_ioctx_destroy(ioctx);
+ rados_shutdown(cluster);
+}
+
+
+
+// ---------------------------------------------
+
+TEST_F(LibRadosList, EnumerateObjects) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+
+ const uint32_t n_objects = 16;
+ for (unsigned i=0; i<n_objects; ++i) {
+ ASSERT_EQ(0, rados_write(ioctx, stringify(i).c_str(), buf, sizeof(buf), 0));
+ }
+
+ // Ensure a non-power-of-two PG count to avoid only
+ // touching the easy path.
+ if (!is_crimson_cluster()) {
+ ASSERT_TRUE(set_pg_num(&s_cluster, pool_name, 11).empty());
+ ASSERT_TRUE(set_pgp_num(&s_cluster, pool_name, 11).empty());
+ }
+ std::set<std::string> saw_obj;
+ rados_object_list_cursor c = rados_object_list_begin(ioctx);
+ rados_object_list_cursor end = rados_object_list_end(ioctx);
+ while(!rados_object_list_is_end(ioctx, c))
+ {
+ rados_object_list_item results[12];
+ memset(results, 0, sizeof(rados_object_list_item) * 12);
+ rados_object_list_cursor temp_end = rados_object_list_end(ioctx);
+ int r = rados_object_list(ioctx, c, temp_end,
+ 12, NULL, 0, results, &c);
+ rados_object_list_cursor_free(ioctx, temp_end);
+ ASSERT_GE(r, 0);
+ for (int i = 0; i < r; ++i) {
+ std::string oid(results[i].oid, results[i].oid_length);
+ if (saw_obj.count(oid)) {
+ std::cerr << "duplicate obj " << oid << std::endl;
+ }
+ ASSERT_FALSE(saw_obj.count(oid));
+ saw_obj.insert(oid);
+ }
+ rados_object_list_free(12, results);
+ }
+ rados_object_list_cursor_free(ioctx, c);
+ rados_object_list_cursor_free(ioctx, end);
+
+ for (unsigned i=0; i<n_objects; ++i) {
+ if (!saw_obj.count(stringify(i))) {
+ std::cerr << "missing object " << i << std::endl;
+ }
+ ASSERT_TRUE(saw_obj.count(stringify(i)));
+ }
+ ASSERT_EQ(n_objects, saw_obj.size());
+}
+
+TEST_F(LibRadosList, EnumerateObjectsSplit) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+
+ const uint32_t n_objects = 16;
+ for (unsigned i=0; i<n_objects; ++i) {
+ ASSERT_EQ(0, rados_write(ioctx, stringify(i).c_str(), buf, sizeof(buf), 0));
+ }
+
+ // Ensure a non-power-of-two PG count to avoid only
+ // touching the easy path.
+ if (!is_crimson_cluster()) {
+ if (auto error = set_pg_num(&s_cluster, pool_name, 11); !error.empty()) {
+ GTEST_FAIL() << error;
+ }
+ if (auto error = set_pgp_num(&s_cluster, pool_name, 11); !error.empty()) {
+ GTEST_FAIL() << error;
+ }
+ }
+
+ rados_object_list_cursor begin = rados_object_list_begin(ioctx);
+ rados_object_list_cursor end = rados_object_list_end(ioctx);
+
+ // Step through an odd number of shards
+ unsigned m = 5;
+ std::set<std::string> saw_obj;
+ for (unsigned n = 0; n < m; ++n) {
+ rados_object_list_cursor shard_start = rados_object_list_begin(ioctx);;
+ rados_object_list_cursor shard_end = rados_object_list_end(ioctx);;
+
+ rados_object_list_slice(
+ ioctx,
+ begin,
+ end,
+ n,
+ m,
+ &shard_start,
+ &shard_end);
+ std::cout << "split " << n << "/" << m << " -> "
+ << *(hobject_t*)shard_start << " "
+ << *(hobject_t*)shard_end << std::endl;
+
+ rados_object_list_cursor c = shard_start;
+ //while(c < shard_end)
+ while(rados_object_list_cursor_cmp(ioctx, c, shard_end) == -1)
+ {
+ rados_object_list_item results[12];
+ memset(results, 0, sizeof(rados_object_list_item) * 12);
+ int r = rados_object_list(ioctx,
+ c, shard_end,
+ 12, NULL, 0, results, &c);
+ ASSERT_GE(r, 0);
+ for (int i = 0; i < r; ++i) {
+ std::string oid(results[i].oid, results[i].oid_length);
+ if (saw_obj.count(oid)) {
+ std::cerr << "duplicate obj " << oid << std::endl;
+ }
+ ASSERT_FALSE(saw_obj.count(oid));
+ saw_obj.insert(oid);
+ }
+ rados_object_list_free(12, results);
+ }
+ rados_object_list_cursor_free(ioctx, shard_start);
+ rados_object_list_cursor_free(ioctx, shard_end);
+ }
+
+ rados_object_list_cursor_free(ioctx, begin);
+ rados_object_list_cursor_free(ioctx, end);
+
+ for (unsigned i=0; i<n_objects; ++i) {
+ if (!saw_obj.count(stringify(i))) {
+ std::cerr << "missing object " << i << std::endl;
+ }
+ ASSERT_TRUE(saw_obj.count(stringify(i)));
+ }
+ ASSERT_EQ(n_objects, saw_obj.size());
+}
diff --git a/src/test/librados/list_cxx.cc b/src/test/librados/list_cxx.cc
new file mode 100644
index 000000000..112231302
--- /dev/null
+++ b/src/test/librados/list_cxx.cc
@@ -0,0 +1,782 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#include <errno.h>
+#include <string>
+#include <stdexcept>
+
+#include "gtest/gtest.h"
+
+#include "include/rados/librados.hpp"
+#include "include/stringify.h"
+#include "include/types.h"
+#include "common/hobject.h"
+#include "test/librados/test_cxx.h"
+#include "test/librados/test_common.h"
+#include "test/librados/testcase_cxx.h"
+#include "global/global_context.h"
+
+#include "crimson_utils.h"
+
+using namespace librados;
+
+typedef RadosTestPPNSCleanup LibRadosListPP;
+typedef RadosTestECPPNSCleanup LibRadosListECPP;
+
+TEST_F(LibRadosListPP, ListObjectsPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ NObjectIterator iter(ioctx.nobjects_begin());
+ bool foundit = false;
+ while (iter != ioctx.nobjects_end()) {
+ foundit = true;
+ ASSERT_EQ((*iter).get_oid(), "foo");
+ ++iter;
+ }
+ ASSERT_TRUE(foundit);
+}
+
+TEST_F(LibRadosListPP, ListObjectsTwicePP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ NObjectIterator iter(ioctx.nobjects_begin());
+ bool foundit = false;
+ while (iter != ioctx.nobjects_end()) {
+ foundit = true;
+ ASSERT_EQ((*iter).get_oid(), "foo");
+ ++iter;
+ }
+ ASSERT_TRUE(foundit);
+ ++iter;
+ ASSERT_TRUE(iter == ioctx.nobjects_end());
+ foundit = false;
+ iter.seek(0);
+ while (iter != ioctx.nobjects_end()) {
+ foundit = true;
+ ASSERT_EQ((*iter).get_oid(), "foo");
+ ++iter;
+ }
+ ASSERT_TRUE(foundit);
+}
+
+TEST_F(LibRadosListPP, ListObjectsCopyIterPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+
+ // make sure this is still valid after the original iterators are gone
+ NObjectIterator iter3;
+ {
+ NObjectIterator iter(ioctx.nobjects_begin());
+ NObjectIterator iter2(iter);
+ iter3 = iter2;
+ ASSERT_EQ((*iter).get_oid(), "foo");
+ ++iter;
+ ASSERT_TRUE(iter == ioctx.nobjects_end());
+ ++iter;
+ ASSERT_TRUE(iter == ioctx.nobjects_end());
+
+ ASSERT_EQ(iter2->get_oid(), "foo");
+ ASSERT_EQ(iter3->get_oid(), "foo");
+ ++iter2;
+ ASSERT_TRUE(iter2 == ioctx.nobjects_end());
+ }
+
+ ASSERT_EQ(iter3->get_oid(), "foo");
+ iter3 = iter3;
+ ASSERT_EQ(iter3->get_oid(), "foo");
+ ++iter3;
+ ASSERT_TRUE(iter3 == ioctx.nobjects_end());
+}
+
+TEST_F(LibRadosListPP, ListObjectsEndIter) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+
+ NObjectIterator iter(ioctx.nobjects_begin());
+ NObjectIterator iter_end(ioctx.nobjects_end());
+ NObjectIterator iter_end2 = ioctx.nobjects_end();
+ ASSERT_TRUE(iter_end == iter_end2);
+ ASSERT_TRUE(iter_end == ioctx.nobjects_end());
+ ASSERT_TRUE(iter_end2 == ioctx.nobjects_end());
+
+ ASSERT_EQ(iter->get_oid(), "foo");
+ ++iter;
+ ASSERT_TRUE(iter == ioctx.nobjects_end());
+ ASSERT_TRUE(iter == iter_end);
+ ASSERT_TRUE(iter == iter_end2);
+ NObjectIterator iter2 = iter;
+ ASSERT_TRUE(iter2 == ioctx.nobjects_end());
+ ASSERT_TRUE(iter2 == iter_end);
+ ASSERT_TRUE(iter2 == iter_end2);
+}
+
+static void check_listpp(std::set<std::string>& myset, IoCtx& ioctx, const std::string &check_nspace)
+{
+ NObjectIterator iter(ioctx.nobjects_begin());
+ std::set<std::string> orig_set(myset);
+ /**
+ * During splitting, we might see duplicate items.
+ * We assert that every object returned is in myset and that
+ * we don't hit ENOENT until we have hit every item in myset
+ * at least once.
+ */
+ while (iter != ioctx.nobjects_end()) {
+ std::string test_name;
+ if (check_nspace == all_nspaces) {
+ test_name = iter->get_nspace() + ":" + iter->get_oid();
+ } else {
+ ASSERT_TRUE(iter->get_nspace() == check_nspace);
+ test_name = iter->get_oid();
+ }
+ ASSERT_TRUE(orig_set.end() != orig_set.find(test_name));
+ myset.erase(test_name);
+ ++iter;
+ }
+ ASSERT_TRUE(myset.empty());
+}
+
+TEST_F(LibRadosListPP, ListObjectsPPNS) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ // Create :foo1, :foo2, :foo3, n1:foo1, ns1:foo4, ns1:foo5, ns2:foo6, n2:foo7
+ ioctx.set_namespace("");
+ ASSERT_EQ(0, ioctx.write("foo1", bl1, sizeof(buf), 0));
+ ioctx.set_namespace("ns1");
+ ASSERT_EQ(0, ioctx.write("foo1", bl1, sizeof(buf), 0));
+ ioctx.set_namespace("");
+ ASSERT_EQ(0, ioctx.write("foo2", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.write("foo3", bl1, sizeof(buf), 0));
+ ioctx.set_namespace("ns1");
+ ASSERT_EQ(0, ioctx.write("foo4", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.write("foo5", bl1, sizeof(buf), 0));
+ ioctx.set_namespace("ns2");
+ ASSERT_EQ(0, ioctx.write("foo6", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.write("foo7", bl1, sizeof(buf), 0));
+ ASSERT_EQ(std::string("ns2"), ioctx.get_namespace());
+
+ std::set<std::string> def, ns1, ns2, all;
+ def.insert(std::string("foo1"));
+ def.insert(std::string("foo2"));
+ def.insert(std::string("foo3"));
+ ns1.insert(std::string("foo1"));
+ ns1.insert(std::string("foo4"));
+ ns1.insert(std::string("foo5"));
+ ns2.insert(std::string("foo6"));
+ ns2.insert(std::string("foo7"));
+ all.insert(std::string(":foo1"));
+ all.insert(std::string(":foo2"));
+ all.insert(std::string(":foo3"));
+ all.insert(std::string("ns1:foo1"));
+ all.insert(std::string("ns1:foo4"));
+ all.insert(std::string("ns1:foo5"));
+ all.insert(std::string("ns2:foo6"));
+ all.insert(std::string("ns2:foo7"));
+
+ ioctx.set_namespace("");
+ check_listpp(def, ioctx, "");
+
+ ioctx.set_namespace("ns1");
+ check_listpp(ns1, ioctx, "ns1");
+
+ ioctx.set_namespace("ns2");
+ check_listpp(ns2, ioctx, "ns2");
+
+ ioctx.set_namespace(all_nspaces);
+ check_listpp(all, ioctx, all_nspaces);
+}
+
+TEST_F(LibRadosListPP, ListObjectsManyPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ for (int i=0; i<256; ++i) {
+ ASSERT_EQ(0, ioctx.write(stringify(i), bl, bl.length(), 0));
+ }
+
+ librados::NObjectIterator it = ioctx.nobjects_begin();
+ std::set<std::string> saw_obj;
+ std::set<int> saw_pg;
+ for (; it != ioctx.nobjects_end(); ++it) {
+ std::cout << it->get_oid()
+ << " " << it.get_pg_hash_position() << std::endl;
+ saw_obj.insert(it->get_oid());
+ saw_pg.insert(it.get_pg_hash_position());
+ }
+ std::cout << "saw " << saw_pg.size() << " pgs " << std::endl;
+
+ // make sure they are 0..n
+ for (unsigned i = 0; i < saw_pg.size(); ++i)
+ ASSERT_TRUE(saw_pg.count(i));
+}
+
+TEST_F(LibRadosListPP, ListObjectsStartPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ for (int i=0; i<16; ++i) {
+ ASSERT_EQ(0, ioctx.write(stringify(i), bl, bl.length(), 0));
+ }
+
+ librados::NObjectIterator it = ioctx.nobjects_begin();
+ std::map<int, std::set<std::string> > pg_to_obj;
+ for (; it != ioctx.nobjects_end(); ++it) {
+ std::cout << it->get_oid() << " " << it.get_pg_hash_position() << std::endl;
+ pg_to_obj[it.get_pg_hash_position()].insert(it->get_oid());
+ }
+
+ std::map<int, std::set<std::string> >::reverse_iterator p =
+ pg_to_obj.rbegin();
+ it = ioctx.nobjects_begin(p->first);
+ while (p != pg_to_obj.rend()) {
+ ASSERT_EQ((uint32_t)p->first, it.seek(p->first));
+ std::cout << "have " << it->get_oid() << " expect one of " << p->second << std::endl;
+ ASSERT_TRUE(p->second.count(it->get_oid()));
+ ++p;
+ }
+}
+
+TEST_F(LibRadosListPP, ListObjectsCursorNSPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ const int max_objs = 16;
+
+ map<string, string> oid_to_ns;
+
+ for (int i=0; i<max_objs; ++i) {
+ stringstream ss;
+ ss << "ns" << i / 4;
+ ioctx.set_namespace(ss.str());
+ string oid = stringify(i);
+ ASSERT_EQ(0, ioctx.write(oid, bl, bl.length(), 0));
+
+ oid_to_ns[oid] = ss.str();
+ }
+
+ ioctx.set_namespace(all_nspaces);
+
+ librados::NObjectIterator it = ioctx.nobjects_begin();
+ std::map<librados::ObjectCursor, string> cursor_to_obj;
+
+ int count = 0;
+
+ librados::ObjectCursor seek_cursor;
+
+ map<string, list<librados::ObjectCursor> > ns_to_cursors;
+
+ for (it = ioctx.nobjects_begin(); it != ioctx.nobjects_end(); ++it) {
+ librados::ObjectCursor cursor = it.get_cursor();
+ string oid = it->get_oid();
+ cout << "> oid=" << oid << " cursor=" << it.get_cursor() << std::endl;
+ }
+
+ vector<string> objs_order;
+
+ for (it = ioctx.nobjects_begin(); it != ioctx.nobjects_end(); ++it, ++count) {
+ librados::ObjectCursor cursor = it.get_cursor();
+ string oid = it->get_oid();
+ std::cout << oid << " " << it.get_pg_hash_position() << std::endl;
+ cout << ": oid=" << oid << " cursor=" << it.get_cursor() << std::endl;
+ cursor_to_obj[cursor] = oid;
+
+ ASSERT_EQ(oid_to_ns[oid], it->get_nspace());
+
+ it.seek(cursor);
+ cout << ": seek to " << cursor << " it.cursor=" << it.get_cursor() << std::endl;
+ ASSERT_EQ(oid, it->get_oid());
+ ASSERT_LT(count, max_objs); /* avoid infinite loops due to bad seek */
+
+ ns_to_cursors[it->get_nspace()].push_back(cursor);
+
+ if (count == max_objs/2) {
+ seek_cursor = cursor;
+ }
+ objs_order.push_back(it->get_oid());
+ }
+
+ ASSERT_EQ(count, max_objs);
+
+ /* check that reading past seek also works */
+ cout << "seek_cursor=" << seek_cursor << std::endl;
+ it.seek(seek_cursor);
+ for (count = max_objs/2; count < max_objs; ++count, ++it) {
+ ASSERT_EQ(objs_order[count], it->get_oid());
+ }
+
+ /* seek to all cursors, check that we get expected obj */
+ for (auto& niter : ns_to_cursors) {
+ const string& ns = niter.first;
+ list<librados::ObjectCursor>& cursors = niter.second;
+
+ for (auto& cursor : cursors) {
+ cout << ": seek to " << cursor << std::endl;
+ it.seek(cursor);
+ ASSERT_EQ(cursor, it.get_cursor());
+ string& expected_oid = cursor_to_obj[cursor];
+ cout << ": it->get_cursor()=" << it.get_cursor() << " expected=" << cursor << std::endl;
+ cout << ": it->get_oid()=" << it->get_oid() << " expected=" << expected_oid << std::endl;
+ cout << ": it->get_nspace()=" << it->get_oid() << " expected=" << ns << std::endl;
+ ASSERT_EQ(expected_oid, it->get_oid());
+ ASSERT_EQ(it->get_nspace(), ns);
+ }
+ }
+}
+
+TEST_F(LibRadosListPP, ListObjectsCursorPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ const int max_objs = 16;
+
+ for (int i=0; i<max_objs; ++i) {
+ stringstream ss;
+ ss << "ns" << i / 4;
+ ioctx.set_namespace(ss.str());
+ ASSERT_EQ(0, ioctx.write(stringify(i), bl, bl.length(), 0));
+ }
+
+ ioctx.set_namespace(all_nspaces);
+
+ librados::NObjectIterator it = ioctx.nobjects_begin();
+ std::map<librados::ObjectCursor, string> cursor_to_obj;
+
+ int count = 0;
+
+ for (; it != ioctx.nobjects_end(); ++it, ++count) {
+ librados::ObjectCursor cursor = it.get_cursor();
+ string oid = it->get_oid();
+ std::cout << oid << " " << it.get_pg_hash_position() << std::endl;
+ cout << ": oid=" << oid << " cursor=" << it.get_cursor() << std::endl;
+ cursor_to_obj[cursor] = oid;
+
+ it.seek(cursor);
+ cout << ": seek to " << cursor << std::endl;
+ ASSERT_EQ(oid, it->get_oid());
+ ASSERT_LT(count, max_objs); /* avoid infinite loops due to bad seek */
+ }
+
+ ASSERT_EQ(count, max_objs);
+
+ auto p = cursor_to_obj.rbegin();
+ it = ioctx.nobjects_begin();
+ while (p != cursor_to_obj.rend()) {
+ cout << ": seek to " << p->first << std::endl;
+ it.seek(p->first);
+ ASSERT_EQ(p->first, it.get_cursor());
+ cout << ": it->get_cursor()=" << it.get_cursor() << " expected=" << p->first << std::endl;
+ cout << ": it->get_oid()=" << it->get_oid() << " expected=" << p->second << std::endl;
+ ASSERT_EQ(p->second, it->get_oid());
+
+ librados::NObjectIterator it2 = ioctx.nobjects_begin(it.get_cursor());
+ ASSERT_EQ(it2->get_oid(), it->get_oid());
+
+ ++p;
+ }
+}
+
+TEST_F(LibRadosListECPP, ListObjectsPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ NObjectIterator iter(ioctx.nobjects_begin());
+ bool foundit = false;
+ while (iter != ioctx.nobjects_end()) {
+ foundit = true;
+ ASSERT_EQ((*iter).get_oid(), "foo");
+ ++iter;
+ }
+ ASSERT_TRUE(foundit);
+}
+
+TEST_F(LibRadosListECPP, ListObjectsTwicePP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ NObjectIterator iter(ioctx.nobjects_begin());
+ bool foundit = false;
+ while (iter != ioctx.nobjects_end()) {
+ foundit = true;
+ ASSERT_EQ((*iter).get_oid(), "foo");
+ ++iter;
+ }
+ ASSERT_TRUE(foundit);
+ ++iter;
+ ASSERT_TRUE(iter == ioctx.nobjects_end());
+ foundit = false;
+ iter.seek(0);
+ while (iter != ioctx.nobjects_end()) {
+ foundit = true;
+ ASSERT_EQ((*iter).get_oid(), "foo");
+ ++iter;
+ }
+ ASSERT_TRUE(foundit);
+}
+
+TEST_F(LibRadosListECPP, ListObjectsCopyIterPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+
+ // make sure this is still valid after the original iterators are gone
+ NObjectIterator iter3;
+ {
+ NObjectIterator iter(ioctx.nobjects_begin());
+ NObjectIterator iter2(iter);
+ iter3 = iter2;
+ ASSERT_EQ((*iter).get_oid(), "foo");
+ ++iter;
+ ASSERT_TRUE(iter == ioctx.nobjects_end());
+ ++iter;
+ ASSERT_TRUE(iter == ioctx.nobjects_end());
+
+ ASSERT_EQ(iter2->get_oid(), "foo");
+ ASSERT_EQ(iter3->get_oid(), "foo");
+ ++iter2;
+ ASSERT_TRUE(iter2 == ioctx.nobjects_end());
+ }
+
+ ASSERT_EQ(iter3->get_oid(), "foo");
+ iter3 = iter3;
+ ASSERT_EQ(iter3->get_oid(), "foo");
+ ++iter3;
+ ASSERT_TRUE(iter3 == ioctx.nobjects_end());
+}
+
+TEST_F(LibRadosListECPP, ListObjectsEndIter) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+
+ NObjectIterator iter(ioctx.nobjects_begin());
+ NObjectIterator iter_end(ioctx.nobjects_end());
+ NObjectIterator iter_end2 = ioctx.nobjects_end();
+ ASSERT_TRUE(iter_end == iter_end2);
+ ASSERT_TRUE(iter_end == ioctx.nobjects_end());
+ ASSERT_TRUE(iter_end2 == ioctx.nobjects_end());
+
+ ASSERT_EQ(iter->get_oid(), "foo");
+ ++iter;
+ ASSERT_TRUE(iter == ioctx.nobjects_end());
+ ASSERT_TRUE(iter == iter_end);
+ ASSERT_TRUE(iter == iter_end2);
+ NObjectIterator iter2 = iter;
+ ASSERT_TRUE(iter2 == ioctx.nobjects_end());
+ ASSERT_TRUE(iter2 == iter_end);
+ ASSERT_TRUE(iter2 == iter_end2);
+}
+
+TEST_F(LibRadosListECPP, ListObjectsPPNS) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ // Create :foo1, :foo2, :foo3, n1:foo1, ns1:foo4, ns1:foo5, ns2:foo6, n2:foo7
+ ioctx.set_namespace("");
+ ASSERT_EQ(0, ioctx.write("foo1", bl1, sizeof(buf), 0));
+ ioctx.set_namespace("ns1");
+ ASSERT_EQ(0, ioctx.write("foo1", bl1, sizeof(buf), 0));
+ ioctx.set_namespace("");
+ ASSERT_EQ(0, ioctx.write("foo2", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.write("foo3", bl1, sizeof(buf), 0));
+ ioctx.set_namespace("ns1");
+ ASSERT_EQ(0, ioctx.write("foo4", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.write("foo5", bl1, sizeof(buf), 0));
+ ioctx.set_namespace("ns2");
+ ASSERT_EQ(0, ioctx.write("foo6", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.write("foo7", bl1, sizeof(buf), 0));
+
+ std::set<std::string> def, ns1, ns2;
+ def.insert(std::string("foo1"));
+ def.insert(std::string("foo2"));
+ def.insert(std::string("foo3"));
+ ns1.insert(std::string("foo1"));
+ ns1.insert(std::string("foo4"));
+ ns1.insert(std::string("foo5"));
+ ns2.insert(std::string("foo6"));
+ ns2.insert(std::string("foo7"));
+
+ ioctx.set_namespace("");
+ check_listpp(def, ioctx, "");
+
+ ioctx.set_namespace("ns1");
+ check_listpp(ns1, ioctx, "ns1");
+
+ ioctx.set_namespace("ns2");
+ check_listpp(ns2, ioctx, "ns2");
+}
+
+TEST_F(LibRadosListECPP, ListObjectsManyPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ for (int i=0; i<256; ++i) {
+ ASSERT_EQ(0, ioctx.write(stringify(i), bl, bl.length(), 0));
+ }
+
+ librados::NObjectIterator it = ioctx.nobjects_begin();
+ std::set<std::string> saw_obj;
+ std::set<int> saw_pg;
+ for (; it != ioctx.nobjects_end(); ++it) {
+ std::cout << it->get_oid()
+ << " " << it.get_pg_hash_position() << std::endl;
+ saw_obj.insert(it->get_oid());
+ saw_pg.insert(it.get_pg_hash_position());
+ }
+ std::cout << "saw " << saw_pg.size() << " pgs " << std::endl;
+
+ // make sure they are 0..n
+ for (unsigned i = 0; i < saw_pg.size(); ++i)
+ ASSERT_TRUE(saw_pg.count(i));
+}
+
+TEST_F(LibRadosListECPP, ListObjectsStartPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ for (int i=0; i<16; ++i) {
+ ASSERT_EQ(0, ioctx.write(stringify(i), bl, bl.length(), 0));
+ }
+
+ librados::NObjectIterator it = ioctx.nobjects_begin();
+ std::map<int, std::set<std::string> > pg_to_obj;
+ for (; it != ioctx.nobjects_end(); ++it) {
+ std::cout << it->get_oid() << " " << it.get_pg_hash_position() << std::endl;
+ pg_to_obj[it.get_pg_hash_position()].insert(it->get_oid());
+ }
+
+ std::map<int, std::set<std::string> >::reverse_iterator p =
+ pg_to_obj.rbegin();
+ it = ioctx.nobjects_begin(p->first);
+ while (p != pg_to_obj.rend()) {
+ ASSERT_EQ((uint32_t)p->first, it.seek(p->first));
+ std::cout << "have " << it->get_oid() << " expect one of " << p->second << std::endl;
+ ASSERT_TRUE(p->second.count(it->get_oid()));
+ ++p;
+ }
+}
+
+TEST_F(LibRadosListPP, ListObjectsFilterPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist obj_content;
+ obj_content.append(buf, sizeof(buf));
+
+ std::string target_str = "content";
+
+ // Write xattr bare, no ::encod'ing
+ bufferlist target_val;
+ target_val.append(target_str);
+ bufferlist nontarget_val;
+ nontarget_val.append("rhubarb");
+
+ ASSERT_EQ(0, ioctx.write("has_xattr", obj_content, obj_content.length(), 0));
+ ASSERT_EQ(0, ioctx.write("has_wrong_xattr", obj_content, obj_content.length(), 0));
+ ASSERT_EQ(0, ioctx.write("no_xattr", obj_content, obj_content.length(), 0));
+
+ ASSERT_EQ(0, ioctx.setxattr("has_xattr", "theattr", target_val));
+ ASSERT_EQ(0, ioctx.setxattr("has_wrong_xattr", "theattr", nontarget_val));
+
+ bufferlist filter_bl;
+ std::string filter_name = "plain";
+ encode(filter_name, filter_bl);
+ encode("_theattr", filter_bl);
+ encode(target_str, filter_bl);
+
+ NObjectIterator iter(ioctx.nobjects_begin(filter_bl));
+ bool foundit = false;
+ int k = 0;
+ while (iter != ioctx.nobjects_end()) {
+ foundit = true;
+ // We should only see the object that matches the filter
+ ASSERT_EQ((*iter).get_oid(), "has_xattr");
+ // We should only see it once
+ ASSERT_EQ(k, 0);
+ ++iter;
+ ++k;
+ }
+ ASSERT_TRUE(foundit);
+}
+
+TEST_F(LibRadosListPP, EnumerateObjectsPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ const uint32_t n_objects = 16;
+ for (unsigned i=0; i<n_objects; ++i) {
+ ASSERT_EQ(0, ioctx.write(stringify(i), bl, sizeof(buf), 0));
+ }
+
+ std::set<std::string> saw_obj;
+ ObjectCursor c = ioctx.object_list_begin();
+ ObjectCursor end = ioctx.object_list_end();
+ while(!ioctx.object_list_is_end(c))
+ {
+ std::vector<ObjectItem> result;
+ int r = ioctx.object_list(c, end, 12, {}, &result, &c);
+ ASSERT_GE(r, 0);
+ ASSERT_EQ(r, (int)result.size());
+ for (int i = 0; i < r; ++i) {
+ auto oid = result[i].oid;
+ if (saw_obj.count(oid)) {
+ std::cerr << "duplicate obj " << oid << std::endl;
+ }
+ ASSERT_FALSE(saw_obj.count(oid));
+ saw_obj.insert(oid);
+ }
+ }
+
+ for (unsigned i=0; i<n_objects; ++i) {
+ if (!saw_obj.count(stringify(i))) {
+ std::cerr << "missing object " << i << std::endl;
+ }
+ ASSERT_TRUE(saw_obj.count(stringify(i)));
+ }
+ ASSERT_EQ(n_objects, saw_obj.size());
+}
+
+TEST_F(LibRadosListPP, EnumerateObjectsSplitPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ const uint32_t n_objects = 16;
+ for (unsigned i=0; i<n_objects; ++i) {
+ ASSERT_EQ(0, ioctx.write(stringify(i), bl, sizeof(buf), 0));
+ }
+
+ ObjectCursor begin = ioctx.object_list_begin();
+ ObjectCursor end = ioctx.object_list_end();
+
+ // Step through an odd number of shards
+ unsigned m = 5;
+ std::set<std::string> saw_obj;
+ for (unsigned n = 0; n < m; ++n) {
+ ObjectCursor shard_start;
+ ObjectCursor shard_end;
+
+ ioctx.object_list_slice(
+ begin,
+ end,
+ n,
+ m,
+ &shard_start,
+ &shard_end);
+
+ ObjectCursor c(shard_start);
+ while(c < shard_end)
+ {
+ std::vector<ObjectItem> result;
+ int r = ioctx.object_list(c, shard_end, 12, {}, &result, &c);
+ ASSERT_GE(r, 0);
+
+ for (const auto & i : result) {
+ const auto &oid = i.oid;
+ if (saw_obj.count(oid)) {
+ std::cerr << "duplicate obj " << oid << std::endl;
+ }
+ ASSERT_FALSE(saw_obj.count(oid));
+ saw_obj.insert(oid);
+ }
+ }
+ }
+
+ for (unsigned i=0; i<n_objects; ++i) {
+ if (!saw_obj.count(stringify(i))) {
+ std::cerr << "missing object " << i << std::endl;
+ }
+ ASSERT_TRUE(saw_obj.count(stringify(i)));
+ }
+ ASSERT_EQ(n_objects, saw_obj.size());
+}
+
+
+TEST_F(LibRadosListPP, EnumerateObjectsFilterPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist obj_content;
+ obj_content.append(buf, sizeof(buf));
+
+ std::string target_str = "content";
+
+ // Write xattr bare, no ::encod'ing
+ bufferlist target_val;
+ target_val.append(target_str);
+ bufferlist nontarget_val;
+ nontarget_val.append("rhubarb");
+
+ ASSERT_EQ(0, ioctx.write("has_xattr", obj_content, obj_content.length(), 0));
+ ASSERT_EQ(0, ioctx.write("has_wrong_xattr", obj_content, obj_content.length(), 0));
+ ASSERT_EQ(0, ioctx.write("no_xattr", obj_content, obj_content.length(), 0));
+
+ ASSERT_EQ(0, ioctx.setxattr("has_xattr", "theattr", target_val));
+ ASSERT_EQ(0, ioctx.setxattr("has_wrong_xattr", "theattr", nontarget_val));
+
+ bufferlist filter_bl;
+ std::string filter_name = "plain";
+ encode(filter_name, filter_bl);
+ encode("_theattr", filter_bl);
+ encode(target_str, filter_bl);
+
+ ObjectCursor c = ioctx.object_list_begin();
+ ObjectCursor end = ioctx.object_list_end();
+ bool foundit = false;
+ while(!ioctx.object_list_is_end(c))
+ {
+ std::vector<ObjectItem> result;
+ int r = ioctx.object_list(c, end, 12, filter_bl, &result, &c);
+ ASSERT_GE(r, 0);
+ ASSERT_EQ(r, (int)result.size());
+ for (int i = 0; i < r; ++i) {
+ auto oid = result[i].oid;
+ // We should only see the object that matches the filter
+ ASSERT_EQ(oid, "has_xattr");
+ // We should only see it once
+ ASSERT_FALSE(foundit);
+ foundit = true;
+ }
+ }
+ ASSERT_TRUE(foundit);
+}
diff --git a/src/test/librados/lock.cc b/src/test/librados/lock.cc
new file mode 100644
index 000000000..a6ac36365
--- /dev/null
+++ b/src/test/librados/lock.cc
@@ -0,0 +1,237 @@
+#include "include/rados/librados.h"
+#include "test/librados/test.h"
+#include "test/librados/TestCase.h"
+#include "cls/lock/cls_lock_client.h"
+
+#include <algorithm>
+#include <chrono>
+#include <thread>
+#include <errno.h>
+#include "gtest/gtest.h"
+#include <sys/time.h>
+
+#include "crimson_utils.h"
+
+using namespace std::chrono_literals;
+
+typedef RadosTest LibRadosLock;
+typedef RadosTestEC LibRadosLockEC;
+
+
+TEST_F(LibRadosLock, LockExclusive) {
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLock1", "Cookie", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, rados_lock_exclusive(ioctx, "foo", "TestLock1", "Cookie", "", NULL, 0));
+}
+
+TEST_F(LibRadosLock, LockShared) {
+ ASSERT_EQ(0, rados_lock_shared(ioctx, "foo", "TestLock2", "Cookie", "Tag", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, rados_lock_shared(ioctx, "foo", "TestLock2", "Cookie", "Tag", "", NULL, 0));
+}
+
+TEST_F(LibRadosLock, LockExclusiveDur) {
+ struct timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ auto lock_exclusive = [this](timeval* tv) {
+ return rados_lock_exclusive(ioctx, "foo", "TestLock3", "Cookie", "", tv, 0);
+ };
+ constexpr int expected = 0;
+ ASSERT_EQ(expected, lock_exclusive(&tv));
+ ASSERT_EQ(expected, wait_until(1.0s, 0.1s, expected, lock_exclusive, nullptr));
+}
+
+TEST_F(LibRadosLock, LockSharedDur) {
+ struct timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ auto lock_shared = [this](timeval* tv) {
+ return rados_lock_shared(ioctx, "foo", "TestLock4", "Cookie", "Tag", "", tv, 0);
+ };
+ constexpr int expected = 0;
+ ASSERT_EQ(expected, lock_shared(&tv));
+ ASSERT_EQ(expected, wait_until(1.0s, 0.1s, expected, lock_shared, nullptr));
+}
+
+
+TEST_F(LibRadosLock, LockMayRenew) {
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLock5", "Cookie", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, rados_lock_exclusive(ioctx, "foo", "TestLock5", "Cookie", "", NULL, 0));
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLock5", "Cookie", "", NULL, LOCK_FLAG_MAY_RENEW));
+}
+
+TEST_F(LibRadosLock, Unlock) {
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLock6", "Cookie", "", NULL, 0));
+ ASSERT_EQ(0, rados_unlock(ioctx, "foo", "TestLock6", "Cookie"));
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLock6", "Cookie", "", NULL, 0));
+}
+
+TEST_F(LibRadosLock, ListLockers) {
+ int exclusive;
+ char tag[1024];
+ char clients[1024];
+ char cookies[1024];
+ char addresses[1024];
+ size_t tag_len = 1024;
+ size_t clients_len = 1024;
+ size_t cookies_len = 1024;
+ size_t addresses_len = 1024;
+ std::stringstream sstm;
+ sstm << "client." << rados_get_instance_id(cluster);
+ std::string me = sstm.str();
+ ASSERT_EQ(0, rados_lock_shared(ioctx, "foo", "TestLock7", "Cookie", "Tag", "", NULL, 0));
+ ASSERT_EQ(0, rados_unlock(ioctx, "foo", "TestLock7", "Cookie"));
+ ASSERT_EQ(0, rados_list_lockers(ioctx, "foo", "TestLock7", &exclusive, tag, &tag_len, clients, &clients_len, cookies, &cookies_len, addresses, &addresses_len ));
+ ASSERT_EQ(0, rados_lock_shared(ioctx, "foo", "TestLock7", "Cookie", "Tag", "", NULL, 0));
+ ASSERT_EQ(-34, rados_list_lockers(ioctx, "foo", "TestLock7", &exclusive, tag, &tag_len, clients, &clients_len, cookies, &cookies_len, addresses, &addresses_len ));
+ tag_len = 1024;
+ clients_len = 1024;
+ cookies_len = 1024;
+ addresses_len = 1024;
+ ASSERT_EQ(1, rados_list_lockers(ioctx, "foo", "TestLock7", &exclusive, tag, &tag_len, clients, &clients_len, cookies, &cookies_len, addresses, &addresses_len ));
+ ASSERT_EQ(0, exclusive);
+ ASSERT_EQ(0, strcmp(tag, "Tag"));
+ ASSERT_EQ(strlen("Tag") + 1, tag_len);
+ ASSERT_EQ(0, strcmp(me.c_str(), clients));
+ ASSERT_EQ(me.size() + 1, clients_len);
+ ASSERT_EQ(0, strcmp(cookies, "Cookie"));
+ ASSERT_EQ(strlen("Cookie") + 1, cookies_len);
+}
+
+TEST_F(LibRadosLock, BreakLock) {
+ int exclusive;
+ char tag[1024];
+ char clients[1024];
+ char cookies[1024];
+ char addresses[1024];
+ size_t tag_len = 1024;
+ size_t clients_len = 1024;
+ size_t cookies_len = 1024;
+ size_t addresses_len = 1024;
+ std::stringstream sstm;
+ sstm << "client." << rados_get_instance_id(cluster);
+ std::string me = sstm.str();
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLock8", "Cookie", "", NULL, 0));
+ ASSERT_EQ(1, rados_list_lockers(ioctx, "foo", "TestLock8", &exclusive, tag, &tag_len, clients, &clients_len, cookies, &cookies_len, addresses, &addresses_len ));
+ ASSERT_EQ(1, exclusive);
+ ASSERT_EQ(0, strcmp(tag, ""));
+ ASSERT_EQ(1U, tag_len);
+ ASSERT_EQ(0, strcmp(me.c_str(), clients));
+ ASSERT_EQ(me.size() + 1, clients_len);
+ ASSERT_EQ(0, strcmp(cookies, "Cookie"));
+ ASSERT_EQ(strlen("Cookie") + 1, cookies_len);
+ ASSERT_EQ(0, rados_break_lock(ioctx, "foo", "TestLock8", clients, "Cookie"));
+}
+
+// EC testing
+TEST_F(LibRadosLockEC, LockExclusive) {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLockEC1", "Cookie", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, rados_lock_exclusive(ioctx, "foo", "TestLockEC1", "Cookie", "", NULL, 0));
+}
+
+TEST_F(LibRadosLockEC, LockShared) {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, rados_lock_shared(ioctx, "foo", "TestLockEC2", "Cookie", "Tag", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, rados_lock_shared(ioctx, "foo", "TestLockEC2", "Cookie", "Tag", "", NULL, 0));
+}
+
+TEST_F(LibRadosLockEC, LockExclusiveDur) {
+ SKIP_IF_CRIMSON();
+ struct timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ auto lock_exclusive = [this](timeval* tv) {
+ return rados_lock_exclusive(ioctx, "foo", "TestLockEC3", "Cookie", "", tv, 0);
+ };
+ constexpr int expected = 0;
+ ASSERT_EQ(expected, lock_exclusive(&tv));
+ ASSERT_EQ(expected, wait_until(1.0s, 0.1s, expected, lock_exclusive, nullptr));
+}
+
+TEST_F(LibRadosLockEC, LockSharedDur) {
+ SKIP_IF_CRIMSON();
+ struct timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ auto lock_shared = [this](timeval* tv) {
+ return rados_lock_shared(ioctx, "foo", "TestLockEC4", "Cookie", "Tag", "", tv, 0);
+ };
+ constexpr int expected = 0;
+ ASSERT_EQ(expected, lock_shared(&tv));
+ ASSERT_EQ(expected, wait_until(1.0s, 0.1s, expected, lock_shared, nullptr));
+}
+
+
+TEST_F(LibRadosLockEC, LockMayRenew) {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLockEC5", "Cookie", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, rados_lock_exclusive(ioctx, "foo", "TestLockEC5", "Cookie", "", NULL, 0));
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLockEC5", "Cookie", "", NULL, LOCK_FLAG_MAY_RENEW));
+}
+
+TEST_F(LibRadosLockEC, Unlock) {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLockEC6", "Cookie", "", NULL, 0));
+ ASSERT_EQ(0, rados_unlock(ioctx, "foo", "TestLockEC6", "Cookie"));
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLockEC6", "Cookie", "", NULL, 0));
+}
+
+TEST_F(LibRadosLockEC, ListLockers) {
+ SKIP_IF_CRIMSON();
+ int exclusive;
+ char tag[1024];
+ char clients[1024];
+ char cookies[1024];
+ char addresses[1024];
+ size_t tag_len = 1024;
+ size_t clients_len = 1024;
+ size_t cookies_len = 1024;
+ size_t addresses_len = 1024;
+ std::stringstream sstm;
+ sstm << "client." << rados_get_instance_id(cluster);
+ std::string me = sstm.str();
+ ASSERT_EQ(0, rados_lock_shared(ioctx, "foo", "TestLockEC7", "Cookie", "Tag", "", NULL, 0));
+ ASSERT_EQ(0, rados_unlock(ioctx, "foo", "TestLockEC7", "Cookie"));
+ ASSERT_EQ(0, rados_list_lockers(ioctx, "foo", "TestLockEC7", &exclusive, tag, &tag_len, clients, &clients_len, cookies, &cookies_len, addresses, &addresses_len ));
+ ASSERT_EQ(0, rados_lock_shared(ioctx, "foo", "TestLockEC7", "Cookie", "Tag", "", NULL, 0));
+ ASSERT_EQ(-34, rados_list_lockers(ioctx, "foo", "TestLockEC7", &exclusive, tag, &tag_len, clients, &clients_len, cookies, &cookies_len, addresses, &addresses_len ));
+ tag_len = 1024;
+ clients_len = 1024;
+ cookies_len = 1024;
+ addresses_len = 1024;
+ ASSERT_EQ(1, rados_list_lockers(ioctx, "foo", "TestLockEC7", &exclusive, tag, &tag_len, clients, &clients_len, cookies, &cookies_len, addresses, &addresses_len ));
+ ASSERT_EQ(0, exclusive);
+ ASSERT_EQ(0, strcmp(tag, "Tag"));
+ ASSERT_EQ(strlen("Tag") + 1, tag_len);
+ ASSERT_EQ(0, strcmp(me.c_str(), clients));
+ ASSERT_EQ(me.size() + 1, clients_len);
+ ASSERT_EQ(0, strcmp(cookies, "Cookie"));
+ ASSERT_EQ(strlen("Cookie") + 1, cookies_len);
+}
+
+TEST_F(LibRadosLockEC, BreakLock) {
+ SKIP_IF_CRIMSON();
+ int exclusive;
+ char tag[1024];
+ char clients[1024];
+ char cookies[1024];
+ char addresses[1024];
+ size_t tag_len = 1024;
+ size_t clients_len = 1024;
+ size_t cookies_len = 1024;
+ size_t addresses_len = 1024;
+ std::stringstream sstm;
+ sstm << "client." << rados_get_instance_id(cluster);
+ std::string me = sstm.str();
+ ASSERT_EQ(0, rados_lock_exclusive(ioctx, "foo", "TestLockEC8", "Cookie", "", NULL, 0));
+ ASSERT_EQ(1, rados_list_lockers(ioctx, "foo", "TestLockEC8", &exclusive, tag, &tag_len, clients, &clients_len, cookies, &cookies_len, addresses, &addresses_len ));
+ ASSERT_EQ(1, exclusive);
+ ASSERT_EQ(0, strcmp(tag, ""));
+ ASSERT_EQ(1U, tag_len);
+ ASSERT_EQ(0, strcmp(me.c_str(), clients));
+ ASSERT_EQ(me.size() + 1, clients_len);
+ ASSERT_EQ(0, strcmp(cookies, "Cookie"));
+ ASSERT_EQ(strlen("Cookie") + 1, cookies_len);
+ ASSERT_EQ(0, rados_break_lock(ioctx, "foo", "TestLockEC8", clients, "Cookie"));
+}
+
diff --git a/src/test/librados/lock_cxx.cc b/src/test/librados/lock_cxx.cc
new file mode 100644
index 000000000..0267ea938
--- /dev/null
+++ b/src/test/librados/lock_cxx.cc
@@ -0,0 +1,203 @@
+#include <algorithm>
+#include <chrono>
+#include <thread>
+#include <errno.h>
+#include <sys/time.h>
+#include "gtest/gtest.h"
+
+#include "include/rados/librados.hpp"
+#include "cls/lock/cls_lock_client.h"
+
+#include "test/librados/test_cxx.h"
+#include "test/librados/testcase_cxx.h"
+
+#include "crimson_utils.h"
+
+using namespace std::chrono_literals;
+using namespace librados;
+
+typedef RadosTestPP LibRadosLockPP;
+typedef RadosTestECPP LibRadosLockECPP;
+
+TEST_F(LibRadosLockPP, LockExclusivePP) {
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockPP1", "Cookie", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, ioctx.lock_exclusive("foo", "TestLockPP1", "Cookie", "", NULL, 0));
+}
+
+TEST_F(LibRadosLockPP, LockSharedPP) {
+ ASSERT_EQ(0, ioctx.lock_shared("foo", "TestLockPP2", "Cookie", "Tag", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, ioctx.lock_shared("foo", "TestLockPP2", "Cookie", "Tag", "", NULL, 0));
+}
+
+TEST_F(LibRadosLockPP, LockExclusiveDurPP) {
+ struct timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ auto lock_exclusive = [this](timeval* tv) {
+ return ioctx.lock_exclusive("foo", "TestLockPP3", "Cookie", "", tv, 0);
+ };
+ constexpr int expected = 0;
+ ASSERT_EQ(expected, lock_exclusive(&tv));
+ ASSERT_EQ(expected, wait_until(1.0s, 0.1s, expected, lock_exclusive, nullptr));
+}
+
+TEST_F(LibRadosLockPP, LockSharedDurPP) {
+ struct timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ auto lock_shared = [this](timeval* tv) {
+ return ioctx.lock_shared("foo", "TestLockPP4", "Cookie", "Tag", "", tv, 0);
+ };
+ constexpr int expected = 0;
+ ASSERT_EQ(expected, lock_shared(&tv));
+ ASSERT_EQ(expected, wait_until(1.0s, 0.1s, expected, lock_shared, nullptr));
+}
+
+TEST_F(LibRadosLockPP, LockMayRenewPP) {
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockPP5", "Cookie", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, ioctx.lock_exclusive("foo", "TestLockPP5", "Cookie", "", NULL, 0));
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockPP5", "Cookie", "", NULL, LOCK_FLAG_MAY_RENEW));
+}
+
+TEST_F(LibRadosLockPP, UnlockPP) {
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockPP6", "Cookie", "", NULL, 0));
+ ASSERT_EQ(0, ioctx.unlock("foo", "TestLockPP6", "Cookie"));
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockPP6", "Cookie", "", NULL, 0));
+}
+
+TEST_F(LibRadosLockPP, ListLockersPP) {
+ std::stringstream sstm;
+ sstm << "client." << cluster.get_instance_id();
+ std::string me = sstm.str();
+ ASSERT_EQ(0, ioctx.lock_shared("foo", "TestLockPP7", "Cookie", "Tag", "", NULL, 0));
+ ASSERT_EQ(0, ioctx.unlock("foo", "TestLockPP7", "Cookie"));
+ {
+ int exclusive;
+ std::string tag;
+ std::list<librados::locker_t> lockers;
+ ASSERT_EQ(0, ioctx.list_lockers("foo", "TestLockPP7", &exclusive, &tag, &lockers));
+ }
+ ASSERT_EQ(0, ioctx.lock_shared("foo", "TestLockPP7", "Cookie", "Tag", "", NULL, 0));
+ {
+ int exclusive;
+ std::string tag;
+ std::list<librados::locker_t> lockers;
+ ASSERT_EQ(1, ioctx.list_lockers("foo", "TestLockPP7", &exclusive, &tag, &lockers));
+ std::list<librados::locker_t>::iterator it = lockers.begin();
+ ASSERT_FALSE(lockers.end() == it);
+ ASSERT_EQ(me, it->client);
+ ASSERT_EQ("Cookie", it->cookie);
+ }
+}
+
+TEST_F(LibRadosLockPP, BreakLockPP) {
+ int exclusive;
+ std::string tag;
+ std::list<librados::locker_t> lockers;
+ std::stringstream sstm;
+ sstm << "client." << cluster.get_instance_id();
+ std::string me = sstm.str();
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockPP8", "Cookie", "", NULL, 0));
+ ASSERT_EQ(1, ioctx.list_lockers("foo", "TestLockPP8", &exclusive, &tag, &lockers));
+ std::list<librados::locker_t>::iterator it = lockers.begin();
+ ASSERT_FALSE(lockers.end() == it);
+ ASSERT_EQ(me, it->client);
+ ASSERT_EQ("Cookie", it->cookie);
+ ASSERT_EQ(0, ioctx.break_lock("foo", "TestLockPP8", it->client, "Cookie"));
+}
+
+// EC testing
+TEST_F(LibRadosLockECPP, LockExclusivePP) {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockECPP1", "Cookie", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, ioctx.lock_exclusive("foo", "TestLockECPP1", "Cookie", "", NULL, 0));
+}
+
+TEST_F(LibRadosLockECPP, LockSharedPP) {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, ioctx.lock_shared("foo", "TestLockECPP2", "Cookie", "Tag", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, ioctx.lock_shared("foo", "TestLockECPP2", "Cookie", "Tag", "", NULL, 0));
+}
+
+TEST_F(LibRadosLockECPP, LockExclusiveDurPP) {
+ SKIP_IF_CRIMSON();
+ struct timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ auto lock_exclusive = [this](timeval* tv) {
+ return ioctx.lock_exclusive("foo", "TestLockECPP3", "Cookie", "", tv, 0);
+ };
+ constexpr int expected = 0;
+ ASSERT_EQ(expected, lock_exclusive(&tv));
+ ASSERT_EQ(expected, wait_until(1.0s, 0.1s, expected, lock_exclusive, nullptr));
+}
+
+TEST_F(LibRadosLockECPP, LockSharedDurPP) {
+ SKIP_IF_CRIMSON();
+ struct timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ auto lock_shared = [this](timeval* tv) {
+ return ioctx.lock_shared("foo", "TestLockECPP4", "Cookie", "Tag", "", tv, 0);
+ };
+ const int expected = 0;
+ ASSERT_EQ(expected, lock_shared(&tv));
+ ASSERT_EQ(expected, wait_until(1.0s, 0.1s, expected, lock_shared, nullptr));
+}
+
+TEST_F(LibRadosLockECPP, LockMayRenewPP) {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockECPP5", "Cookie", "", NULL, 0));
+ ASSERT_EQ(-EEXIST, ioctx.lock_exclusive("foo", "TestLockECPP5", "Cookie", "", NULL, 0));
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockECPP5", "Cookie", "", NULL, LOCK_FLAG_MAY_RENEW));
+}
+
+TEST_F(LibRadosLockECPP, UnlockPP) {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockECPP6", "Cookie", "", NULL, 0));
+ ASSERT_EQ(0, ioctx.unlock("foo", "TestLockECPP6", "Cookie"));
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockECPP6", "Cookie", "", NULL, 0));
+}
+
+TEST_F(LibRadosLockECPP, ListLockersPP) {
+ SKIP_IF_CRIMSON();
+ std::stringstream sstm;
+ sstm << "client." << cluster.get_instance_id();
+ std::string me = sstm.str();
+ ASSERT_EQ(0, ioctx.lock_shared("foo", "TestLockECPP7", "Cookie", "Tag", "", NULL, 0));
+ ASSERT_EQ(0, ioctx.unlock("foo", "TestLockECPP7", "Cookie"));
+ {
+ int exclusive;
+ std::string tag;
+ std::list<librados::locker_t> lockers;
+ ASSERT_EQ(0, ioctx.list_lockers("foo", "TestLockECPP7", &exclusive, &tag, &lockers));
+ }
+ ASSERT_EQ(0, ioctx.lock_shared("foo", "TestLockECPP7", "Cookie", "Tag", "", NULL, 0));
+ {
+ int exclusive;
+ std::string tag;
+ std::list<librados::locker_t> lockers;
+ ASSERT_EQ(1, ioctx.list_lockers("foo", "TestLockECPP7", &exclusive, &tag, &lockers));
+ std::list<librados::locker_t>::iterator it = lockers.begin();
+ ASSERT_FALSE(lockers.end() == it);
+ ASSERT_EQ(me, it->client);
+ ASSERT_EQ("Cookie", it->cookie);
+ }
+}
+
+TEST_F(LibRadosLockECPP, BreakLockPP) {
+ SKIP_IF_CRIMSON();
+ int exclusive;
+ std::string tag;
+ std::list<librados::locker_t> lockers;
+ std::stringstream sstm;
+ sstm << "client." << cluster.get_instance_id();
+ std::string me = sstm.str();
+ ASSERT_EQ(0, ioctx.lock_exclusive("foo", "TestLockECPP8", "Cookie", "", NULL, 0));
+ ASSERT_EQ(1, ioctx.list_lockers("foo", "TestLockECPP8", &exclusive, &tag, &lockers));
+ std::list<librados::locker_t>::iterator it = lockers.begin();
+ ASSERT_FALSE(lockers.end() == it);
+ ASSERT_EQ(me, it->client);
+ ASSERT_EQ("Cookie", it->cookie);
+ ASSERT_EQ(0, ioctx.break_lock("foo", "TestLockECPP8", it->client, "Cookie"));
+}
diff --git a/src/test/librados/misc.cc b/src/test/librados/misc.cc
new file mode 100644
index 000000000..d9cb1c5b8
--- /dev/null
+++ b/src/test/librados/misc.cc
@@ -0,0 +1,358 @@
+// -*- 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.h"
+#include "include/rados/librados.h"
+#include "include/rados/librados.hpp"
+#include "include/scope_guard.h"
+#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 <sys/time.h>
+#ifndef _WIN32
+#include <sys/resource.h>
+#endif
+
+#include <errno.h>
+#include <map>
+#include <sstream>
+#include <string>
+#include <regex>
+
+using namespace std;
+using namespace librados;
+
+typedef RadosTest LibRadosMisc;
+
+TEST(LibRadosMiscVersion, Version) {
+ 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(-ENOTCONN, rados_monitor_log(cluster, "error",
+ test_rados_log_cb, NULL));
+
+ ASSERT_EQ(0, rados_connect(cluster));
+ rados_shutdown(cluster);
+
+ ASSERT_EQ(0, rados_create(&cluster, NULL));
+ ASSERT_EQ(-ENOENT, rados_connect(cluster));
+ rados_shutdown(cluster);
+}
+
+TEST(LibRadosMiscConnectFailure, ConnectTimeout) {
+ rados_t cluster;
+
+ ASSERT_EQ(0, rados_create(&cluster, NULL));
+ ASSERT_EQ(0, rados_conf_set(cluster, "mon_host", "255.0.1.2:3456"));
+ ASSERT_EQ(0, rados_conf_set(cluster, "key",
+ "AQAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAA=="));
+ ASSERT_EQ(0, rados_conf_set(cluster, "client_mount_timeout", "2s"));
+
+ utime_t start = ceph_clock_now();
+ ASSERT_EQ(-ETIMEDOUT, rados_connect(cluster));
+ utime_t end = ceph_clock_now();
+
+ utime_t dur = end - start;
+ ASSERT_GE(dur, utime_t(2, 0));
+ ASSERT_LT(dur, utime_t(4, 0));
+
+ 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_conf_set(cluster_a,
+ "objecter_debug_inject_relock_delay", "true"));
+ 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_connect(cluster_b));
+
+ char poolname[80];
+ snprintf(poolname, sizeof(poolname), "poolrace.%d", rand());
+ rados_pool_create(cluster_a, poolname);
+ rados_ioctx_t a;
+ rados_ioctx_create(cluster_a, poolname, &a);
+
+ char pool2name[80];
+ snprintf(pool2name, sizeof(pool2name), "poolrace2.%d", rand());
+ rados_pool_create(cluster_b, pool2name);
+
+ list<rados_completion_t> 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_completion2(nullptr, nullptr, &c);
+ cls.push_back(c);
+ rados_aio_read(a, "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_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(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);
+ auto iter = bl.cbegin();
+ 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(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));
+}
+
+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 (!std::regex_search(result, std::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(LibRadosMisc, MinCompatOSD) {
+ int8_t require_osd_release;
+ ASSERT_EQ(0, rados_get_min_compatible_osd(cluster, &require_osd_release));
+ ASSERT_LE(-1, require_osd_release);
+ ASSERT_GT(CEPH_RELEASE_MAX, require_osd_release);
+}
+
+TEST_F(LibRadosMisc, MinCompatClient) {
+ int8_t min_compat_client;
+ int8_t require_min_compat_client;
+ ASSERT_EQ(0, rados_get_min_compatible_client(cluster,
+ &min_compat_client,
+ &require_min_compat_client));
+ ASSERT_LE(-1, min_compat_client);
+ ASSERT_GT(CEPH_RELEASE_MAX, min_compat_client);
+
+ ASSERT_LE(-1, require_min_compat_client);
+ ASSERT_GT(CEPH_RELEASE_MAX, require_min_compat_client);
+}
+
+static void shutdown_racer_func()
+{
+ const int niter = 32;
+ rados_t rad;
+ int i;
+
+ for (i = 0; i < niter; ++i) {
+ auto r = connect_cluster(&rad);
+ if (getenv("ALLOW_TIMEOUTS")) {
+ ASSERT_TRUE(r == "" || r == "rados_connect failed with error -110");
+ } else {
+ ASSERT_EQ("", r);
+ }
+ rados_shutdown(rad);
+ }
+}
+
+#ifndef _WIN32
+// See trackers #20988 and #42026
+TEST_F(LibRadosMisc, ShutdownRace)
+{
+ const int nthreads = 128;
+ std::thread threads[nthreads];
+
+ // Need a bunch of fd's for this test
+ struct rlimit rold, rnew;
+ ASSERT_EQ(getrlimit(RLIMIT_NOFILE, &rold), 0);
+ rnew = rold;
+ rnew.rlim_cur = rnew.rlim_max;
+ ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &rnew), 0);
+
+ for (int i = 0; i < nthreads; ++i)
+ threads[i] = std::thread(shutdown_racer_func);
+
+ for (int i = 0; i < nthreads; ++i)
+ threads[i].join();
+ ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &rold), 0);
+}
+#endif /* _WIN32 */
diff --git a/src/test/librados/misc_cxx.cc b/src/test/librados/misc_cxx.cc
new file mode 100644
index 000000000..545d5e57b
--- /dev/null
+++ b/src/test/librados/misc_cxx.cc
@@ -0,0 +1,923 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#include <errno.h>
+#include <map>
+#include <sstream>
+#include <string>
+#include <regex>
+
+#include "gtest/gtest.h"
+
+#include "include/err.h"
+#include "include/buffer.h"
+#include "include/rbd_types.h"
+#include "include/rados.h"
+#include "include/rados/librados.hpp"
+#include "include/scope_guard.h"
+#include "include/stringify.h"
+#include "common/Checksummer.h"
+#include "mds/mdstypes.h"
+#include "global/global_context.h"
+#include "test/librados/testcase_cxx.h"
+#include "test/librados/test_cxx.h"
+
+#include "crimson_utils.h"
+
+using namespace std;
+using namespace librados;
+
+typedef RadosTestPP LibRadosMiscPP;
+typedef RadosTestECPP LibRadosMiscECPP;
+
+TEST(LibRadosMiscVersion, VersionPP) {
+ int major, minor, extra;
+ Rados::version(&major, &minor, &extra);
+}
+
+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));
+}
+
+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);
+ auto iter = out.cbegin();
+ 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);
+ 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(std::min<uint64_t>(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) {
+ SKIP_IF_CRIMSON();
+ 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_from("foo", ioctx, uv + 1, LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ASSERT_EQ(-EOVERFLOW, ioctx.operate("foo.copy", &op));
+ }
+ {
+ // pass old version
+ ObjectWriteOperation op;
+ op.copy_from("foo", ioctx, uv - 1, LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
+ ASSERT_EQ(-ERANGE, ioctx.operate("foo.copy", &op));
+ }
+ {
+ ObjectWriteOperation op;
+ op.copy_from("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_from("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_from("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_from("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() {
+ SKIP_IF_CRIMSON();
+ 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() {
+ SKIP_IF_CRIMSON();
+ 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 {
+ SKIP_IF_CRIMSON();
+ RadosTestECPP::SetUp();
+ ASSERT_EQ(0, cluster.ioctx_create(src_pool_name.c_str(), src_ioctx));
+ src_ioctx.set_namespace(nspace);
+ }
+ void TearDown() override {
+ SKIP_IF_CRIMSON();
+ // 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) {
+ SKIP_IF_CRIMSON();
+ 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, 0);
+ 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, 0);
+ ASSERT_EQ(-EOPNOTSUPP, ioctx.operate("bar.copy", &op));
+ }
+}
+
+TEST_F(LibRadosMiscPP, CopyScrubPP) {
+ SKIP_IF_CRIMSON();
+ 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<string, bufferlist> 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, 0);
+ ASSERT_EQ(0, ioctx.operate("small.copy", &op));
+ }
+
+ {
+ ObjectWriteOperation op;
+ op.copy_from("big", ioctx, 0, 0);
+ ASSERT_EQ(0, ioctx.operate("big.copy", &op));
+ }
+
+ {
+ ObjectWriteOperation op;
+ op.copy_from("big2", ioctx, 0, 0);
+ ASSERT_EQ(0, ioctx.operate("big2.copy", &op));
+ }
+
+ {
+ ObjectWriteOperation op;
+ op.copy_from("big3", ioctx, 0, 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));
+}
+
+template <typename T>
+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<char>(rand() % (126 - 33) + 33);
+ }
+ content_bl.append(content);
+ ASSERT_EQ(0, ioctx.write("foo", content_bl, content_bl.length(), 0));
+ }
+};
+
+template <rados_checksum_type_t _type, typename AlgT, typename ValueT>
+class LibRadosChecksumParams {
+public:
+ typedef AlgT alg_t;
+ typedef ValueT value_t;
+ static const rados_checksum_type_t type = _type;
+};
+
+typedef ::testing::Types<
+ LibRadosChecksumParams<LIBRADOS_CHECKSUM_TYPE_XXHASH32,
+ Checksummer::xxhash32, ceph_le32>,
+ LibRadosChecksumParams<LIBRADOS_CHECKSUM_TYPE_XXHASH64,
+ Checksummer::xxhash64, ceph_le64>,
+ LibRadosChecksumParams<LIBRADOS_CHECKSUM_TYPE_CRC32C,
+ Checksummer::crc32c, ceph_le32>
+ > LibRadosChecksumTypes;
+
+TYPED_TEST_SUITE(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<bufferlist> checksum_bls(csum_count);
+ std::vector<int> 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].cbegin();
+ 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<char*>(&expected_value));
+ Checksummer::template calculate<typename TestFixture::alg_t>(
+ 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.cbegin();
+ uint32_t count;
+ decode(count, bl_it);
+ ASSERT_EQ(csum_count, count);
+
+ std::vector<typename TestFixture::value_t> expected_values(csum_count);
+ bufferptr expected_values_bp = buffer::create_static(
+ csum_count * sizeof(typename TestFixture::value_t),
+ reinterpret_cast<char*>(&expected_values[0]));
+
+ Checksummer::template calculate<typename TestFixture::alg_t>(
+ 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(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 (!std::regex_search(outbl.to_str(),
+ std::regex("require_osd_release [l-z]"))) {
+ std::cout << "SKIPPING";
+ return;
+ }
+
+ std::set<std::string> expected_apps = {"rados"};
+ std::set<std::string> 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<std::string, std::string> expected_meta;
+ std::map<std::string, std::string> 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) {
+ SKIP_IF_CRIMSON();
+ 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));
+}
+
+TEST_F(LibRadosMiscPP, MinCompatOSD) {
+ int8_t require_osd_release;
+ ASSERT_EQ(0, cluster.get_min_compatible_osd(&require_osd_release));
+ ASSERT_LE(-1, require_osd_release);
+ ASSERT_GT(CEPH_RELEASE_MAX, require_osd_release);
+}
+
+TEST_F(LibRadosMiscPP, MinCompatClient) {
+ int8_t min_compat_client;
+ int8_t require_min_compat_client;
+ ASSERT_EQ(0, cluster.get_min_compatible_client(&min_compat_client,
+ &require_min_compat_client));
+ ASSERT_LE(-1, min_compat_client);
+ ASSERT_GT(CEPH_RELEASE_MAX, min_compat_client);
+
+ ASSERT_LE(-1, require_min_compat_client);
+ ASSERT_GT(CEPH_RELEASE_MAX, require_min_compat_client);
+}
+
+TEST_F(LibRadosMiscPP, Conf) {
+ const char* const option = "bluestore_throttle_bytes";
+ size_t new_size = 1 << 20;
+ std::string original;
+ ASSERT_EQ(0, cluster.conf_get(option, original));
+ auto restore_setting = make_scope_guard([&] {
+ cluster.conf_set(option, original.c_str());
+ });
+ std::string expected = std::to_string(new_size);
+ ASSERT_EQ(0, cluster.conf_set(option, expected.c_str()));
+ std::string actual;
+ ASSERT_EQ(0, cluster.conf_get(option, actual));
+ ASSERT_EQ(expected, actual);
+}
diff --git a/src/test/librados/op_speed.cc b/src/test/librados/op_speed.cc
new file mode 100644
index 000000000..849a6566f
--- /dev/null
+++ b/src/test/librados/op_speed.cc
@@ -0,0 +1,24 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*
+// vim: ts=8 sw=2 smarttab
+
+#include <cstdint>
+
+#include "include/rados/librados.hpp"
+
+constexpr int to_create = 10'000'000;
+
+int main() {
+ for (int i = 0; i < to_create; ++i) {
+ librados::ObjectReadOperation op;
+ bufferlist bl;
+ std::uint64_t sz;
+ struct timespec tm;
+ std::map<std::string, ceph::buffer::list> xattrs;
+ std::map<std::string, ceph::buffer::list> omap;
+ bool more;
+ op.read(0, 0, &bl, nullptr);
+ op.stat2(&sz, &tm, nullptr);
+ op.getxattrs(&xattrs, nullptr);
+ op.omap_get_vals2({}, 1000, &omap, &more, nullptr);
+ }
+}
diff --git a/src/test/librados/pool.cc b/src/test/librados/pool.cc
new file mode 100644
index 000000000..a85d8f3a8
--- /dev/null
+++ b/src/test/librados/pool.cc
@@ -0,0 +1,186 @@
+#include <errno.h>
+#include <vector>
+#include "crimson_utils.h"
+#include "gtest/gtest.h"
+#include "include/rados/librados.h"
+#include "test/librados/test.h"
+
+#define POOL_LIST_BUF_SZ 32768
+
+TEST(LibRadosPools, PoolList) {
+ char pool_list_buf[POOL_LIST_BUF_SZ];
+ char *buf = pool_list_buf;
+ rados_t cluster;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ ASSERT_LT(rados_pool_list(cluster, buf, POOL_LIST_BUF_SZ), POOL_LIST_BUF_SZ);
+
+ // we can pass a null buffer too.
+ ASSERT_LT(rados_pool_list(cluster, NULL, POOL_LIST_BUF_SZ), POOL_LIST_BUF_SZ);
+
+ bool found_pool = false;
+ int firstlen = 0;
+ while (buf[0] != '\0') {
+ if ((found_pool == false) && (strcmp(buf, pool_name.c_str()) == 0)) {
+ found_pool = true;
+ }
+ if (!firstlen)
+ firstlen = strlen(buf) + 1;
+ buf += strlen(buf) + 1;
+ }
+ ASSERT_EQ(found_pool, true);
+
+ // make sure we honor the buffer size limit
+ buf = pool_list_buf;
+ memset(buf, 0, POOL_LIST_BUF_SZ);
+ ASSERT_LT(rados_pool_list(cluster, buf, firstlen), POOL_LIST_BUF_SZ);
+ ASSERT_NE(0, buf[0]); // include at least one pool name
+ ASSERT_EQ(0, buf[firstlen]); // but don't touch the stopping point
+
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+int64_t rados_pool_lookup(rados_t cluster, const char *pool_name);
+
+TEST(LibRadosPools, PoolLookup) {
+ rados_t cluster;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ ASSERT_LT(0, rados_pool_lookup(cluster, pool_name.c_str()));
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRadosPools, PoolLookup2) {
+ rados_t cluster;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ int64_t pool_id = rados_pool_lookup(cluster, pool_name.c_str());
+ ASSERT_GT(pool_id, 0);
+ rados_ioctx_t ioctx;
+ ASSERT_EQ(0, rados_ioctx_create(cluster, pool_name.c_str(), &ioctx));
+ int64_t pool_id2 = rados_ioctx_get_id(ioctx);
+ ASSERT_EQ(pool_id, pool_id2);
+ rados_ioctx_destroy(ioctx);
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRadosPools, PoolLookupOtherInstance) {
+ rados_t cluster1;
+ ASSERT_EQ("", connect_cluster(&cluster1));
+
+ rados_t cluster2;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster2));
+ int64_t pool_id = rados_pool_lookup(cluster2, pool_name.c_str());
+ ASSERT_GT(pool_id, 0);
+
+ ASSERT_EQ(pool_id, rados_pool_lookup(cluster1, pool_name.c_str()));
+
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster2));
+ rados_shutdown(cluster1);
+}
+
+TEST(LibRadosPools, PoolReverseLookupOtherInstance) {
+ rados_t cluster1;
+ ASSERT_EQ("", connect_cluster(&cluster1));
+
+ rados_t cluster2;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster2));
+ int64_t pool_id = rados_pool_lookup(cluster2, pool_name.c_str());
+ ASSERT_GT(pool_id, 0);
+
+ char buf[100];
+ ASSERT_LT(0, rados_pool_reverse_lookup(cluster1, pool_id, buf, 100));
+ ASSERT_EQ(0, strcmp(buf, pool_name.c_str()));
+
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster2));
+ rados_shutdown(cluster1);
+}
+
+TEST(LibRadosPools, PoolDelete) {
+ rados_t cluster;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ ASSERT_EQ(0, rados_pool_delete(cluster, pool_name.c_str()));
+ ASSERT_GT(0, rados_pool_lookup(cluster, pool_name.c_str()));
+ ASSERT_EQ(0, rados_pool_create(cluster, pool_name.c_str()));
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRadosPools, PoolCreateDelete) {
+ rados_t cluster;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+
+ std::string n = pool_name + "abc123";
+ ASSERT_EQ(0, rados_pool_create(cluster, n.c_str()));
+ ASSERT_EQ(-EEXIST, rados_pool_create(cluster, n.c_str()));
+ ASSERT_EQ(0, rados_pool_delete(cluster, n.c_str()));
+ ASSERT_EQ(-ENOENT, rados_pool_delete(cluster, n.c_str()));
+
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRadosPools, PoolCreateWithCrushRule) {
+ rados_t cluster;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+
+ std::string pool2_name = get_temp_pool_name();
+ ASSERT_EQ(0, rados_pool_create_with_crush_rule(cluster,
+ pool2_name.c_str(), 0));
+ ASSERT_EQ(0, rados_pool_delete(cluster, pool2_name.c_str()));
+
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
+
+TEST(LibRadosPools, PoolGetBaseTier) {
+ SKIP_IF_CRIMSON();
+ rados_t cluster;
+ std::string pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &cluster));
+ std::string tier_pool_name = pool_name + "-cache";
+ ASSERT_EQ(0, rados_pool_create(cluster, tier_pool_name.c_str()));
+
+ int64_t pool_id = rados_pool_lookup(cluster, pool_name.c_str());
+ ASSERT_GE(pool_id, 0);
+
+ int64_t tier_pool_id = rados_pool_lookup(cluster, tier_pool_name.c_str());
+ ASSERT_GE(tier_pool_id, 0);
+
+
+ int64_t base_tier = 0;
+ EXPECT_EQ(0, rados_pool_get_base_tier(cluster, pool_id, &base_tier));
+ EXPECT_EQ(pool_id, base_tier);
+
+ std::string cmdstr = "{\"prefix\": \"osd tier add\", \"pool\": \"" +
+ pool_name + "\", \"tierpool\":\"" + tier_pool_name + "\", \"force_nonempty\":\"\"}";
+ char *cmd[1];
+ cmd[0] = (char *)cmdstr.c_str();
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+
+ cmdstr = "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" +
+ tier_pool_name + "\", \"mode\":\"readonly\"," +
+ " \"yes_i_really_mean_it\": true}";
+ cmd[0] = (char *)cmdstr.c_str();
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+
+ EXPECT_EQ(0, rados_wait_for_latest_osdmap(cluster));
+
+ EXPECT_EQ(0, rados_pool_get_base_tier(cluster, pool_id, &base_tier));
+ EXPECT_EQ(pool_id, base_tier);
+
+ EXPECT_EQ(0, rados_pool_get_base_tier(cluster, tier_pool_id, &base_tier));
+ EXPECT_EQ(pool_id, base_tier);
+
+ int64_t nonexistent_pool_id = (int64_t)((-1ULL) >> 1);
+ EXPECT_EQ(-ENOENT, rados_pool_get_base_tier(cluster, nonexistent_pool_id, &base_tier));
+
+ cmdstr = "{\"prefix\": \"osd tier remove\", \"pool\": \"" +
+ pool_name + "\", \"tierpool\":\"" + tier_pool_name + "\"}";
+ cmd[0] = (char *)cmdstr.c_str();
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+ ASSERT_EQ(0, rados_pool_delete(cluster, tier_pool_name.c_str()));
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &cluster));
+}
diff --git a/src/test/librados/service.cc b/src/test/librados/service.cc
new file mode 100644
index 000000000..6d88f0da9
--- /dev/null
+++ b/src/test/librados/service.cc
@@ -0,0 +1,209 @@
+#include "include/rados/librados.h"
+#include "include/rados/librados.hpp"
+#include "include/stringify.h"
+#include "common/config_proxy.h"
+#include "test/librados/test.h"
+#include "test/librados/TestCase.h"
+#ifndef _WIN32
+#include <sys/resource.h>
+#endif
+
+#include <mutex>
+#include <condition_variable>
+#include <algorithm>
+#include <thread>
+#include <errno.h>
+#include "gtest/gtest.h"
+#include "test/unit.cc"
+
+using namespace std;
+using namespace librados;
+
+TEST(LibRadosService, RegisterEarly) {
+ rados_t cluster;
+ ASSERT_EQ(0, rados_create(&cluster, "admin"));
+ ASSERT_EQ(0, rados_conf_read_file(cluster, NULL));
+ ASSERT_EQ(0, rados_conf_parse_env(cluster, NULL));
+
+ string name = string("pid") + stringify(getpid());
+ ASSERT_EQ(0, rados_service_register(cluster, "laundry", name.c_str(),
+ "foo\0bar\0this\0that\0"));
+ ASSERT_EQ(-EEXIST, rados_service_register(cluster, "laundry", name.c_str(),
+ "foo\0bar\0this\0that\0"));
+
+ ASSERT_EQ(0, rados_connect(cluster));
+ sleep(5);
+ rados_shutdown(cluster);
+}
+
+TEST(LibRadosService, RegisterLate) {
+ rados_t cluster;
+ ASSERT_EQ(0, rados_create(&cluster, "admin"));
+ ASSERT_EQ(0, rados_conf_read_file(cluster, NULL));
+ ASSERT_EQ(0, rados_conf_parse_env(cluster, NULL));
+ ASSERT_EQ(0, rados_connect(cluster));
+
+ string name = string("pid") + stringify(getpid());
+ ASSERT_EQ(0, rados_service_register(cluster, "laundry", name.c_str(),
+ "foo\0bar\0this\0that\0"));
+ ASSERT_EQ(-EEXIST, rados_service_register(cluster, "laundry", name.c_str(),
+ "foo\0bar\0this\0that\0"));
+ rados_shutdown(cluster);
+}
+
+static void status_format_func(const int i, std::mutex &lock,
+ std::condition_variable &cond,
+ int &threads_started, bool &stopped)
+{
+ rados_t cluster;
+ char metadata_buf[4096];
+
+ ASSERT_EQ(0, rados_create(&cluster, "admin"));
+ ASSERT_EQ(0, rados_conf_read_file(cluster, NULL));
+ ASSERT_EQ(0, rados_conf_parse_env(cluster, NULL));
+
+ ASSERT_EQ(0, rados_connect(cluster));
+ if (i == 0) {
+ ASSERT_LT(0, sprintf(metadata_buf, "%s%c%s%c",
+ "foo", '\0', "bar", '\0'));
+ } else if (i == 1) {
+ ASSERT_LT(0, sprintf(metadata_buf, "%s%c%s%c",
+ "daemon_type", '\0', "portal", '\0'));
+ } else if (i == 2) {
+ ASSERT_LT(0, sprintf(metadata_buf, "%s%c%s%c",
+ "daemon_prefix", '\0', "gateway", '\0'));
+ } else {
+ string prefix = string("gw") + stringify(i % 4);
+ string zone = string("z") + stringify(i % 3);
+ ASSERT_LT(0, sprintf(metadata_buf, "%s%c%s%c%s%c%s%c%s%c%s%c%s%c%s%c",
+ "daemon_type", '\0', "portal", '\0',
+ "daemon_prefix", '\0', prefix.c_str(), '\0',
+ "hostname", '\0', prefix.c_str(), '\0',
+ "zone_id", '\0', zone.c_str(), '\0'));
+ }
+ string name = string("rbd/image") + stringify(i);
+ ASSERT_EQ(0, rados_service_register(cluster, "foo", name.c_str(),
+ metadata_buf));
+
+ std::unique_lock<std::mutex> l(lock);
+ threads_started++;
+ cond.notify_all();
+ cond.wait(l, [&stopped] {
+ return stopped;
+ });
+
+ rados_shutdown(cluster);
+}
+
+TEST(LibRadosService, StatusFormat) {
+ const int nthreads = 16;
+ std::thread threads[nthreads];
+ std::mutex lock;
+ std::condition_variable cond;
+ bool stopped = false;
+ int threads_started = 0;
+
+ // no rlimits on Windows
+ #ifndef _WIN32
+ // Need a bunch of fd's for this test
+ struct rlimit rold, rnew;
+ ASSERT_EQ(getrlimit(RLIMIT_NOFILE, &rold), 0);
+ rnew = rold;
+ rnew.rlim_cur = rnew.rlim_max;
+ ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &rnew), 0);
+ #endif
+
+ for (int i = 0; i < nthreads; ++i)
+ threads[i] = std::thread(status_format_func, i, std::ref(lock),
+ std::ref(cond), std::ref(threads_started),
+ std::ref(stopped));
+
+ {
+ std::unique_lock<std::mutex> l(lock);
+ cond.wait(l, [&threads_started] {
+ return nthreads == threads_started;
+ });
+ }
+
+ int retry = 60; // mon thrashing may make this take a long time
+ while (retry) {
+ rados_t cluster;
+
+ ASSERT_EQ(0, rados_create(&cluster, "admin"));
+ ASSERT_EQ(0, rados_conf_read_file(cluster, NULL));
+ ASSERT_EQ(0, rados_conf_parse_env(cluster, NULL));
+
+ ASSERT_EQ(0, rados_connect(cluster));
+ JSONFormatter cmd_f;
+ cmd_f.open_object_section("command");
+ cmd_f.dump_string("prefix", "status");
+ cmd_f.close_section();
+ std::ostringstream cmd_stream;
+ cmd_f.flush(cmd_stream);
+ const std::string serialized_cmd = cmd_stream.str();
+ const char *cmd[2];
+ cmd[1] = NULL;
+ cmd[0] = serialized_cmd.c_str();
+ char *outbuf = NULL;
+ size_t outlen = 0;
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)cmd, 1, "", 0,
+ &outbuf, &outlen, NULL, NULL));
+ std::string out(outbuf, outlen);
+ cout << out << std::endl;
+ bool success = false;
+ auto r1 = out.find("16 portals active (1 hosts, 3 zones)");
+ if (std::string::npos != r1) {
+ success = true;
+ }
+ rados_buffer_free(outbuf);
+ rados_shutdown(cluster);
+
+ if (success || !retry) {
+ break;
+ }
+
+ // wait for 2 seconds to make sure all the
+ // services have been successfully updated
+ // to ceph mon, then retry it.
+ sleep(2);
+ retry--;
+ }
+
+ {
+ std::scoped_lock<std::mutex> l(lock);
+ stopped = true;
+ cond.notify_all();
+ }
+ for (int i = 0; i < nthreads; ++i)
+ threads[i].join();
+
+ ASSERT_NE(0, retry);
+ #ifndef _WIN32
+ ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &rold), 0);
+ #endif
+}
+
+TEST(LibRadosService, Status) {
+ rados_t cluster;
+ ASSERT_EQ(0, rados_create(&cluster, "admin"));
+ ASSERT_EQ(0, rados_conf_read_file(cluster, NULL));
+ ASSERT_EQ(0, rados_conf_parse_env(cluster, NULL));
+
+ ASSERT_EQ(-ENOTCONN, rados_service_update_status(cluster,
+ "testing\0testing\0"));
+
+ ASSERT_EQ(0, rados_connect(cluster));
+ string name = string("pid") + stringify(getpid());
+ ASSERT_EQ(0, rados_service_register(cluster, "laundry", name.c_str(),
+ "foo\0bar\0this\0that\0"));
+
+ for (int i=0; i<20; ++i) {
+ char buffer[1024];
+ snprintf(buffer, sizeof(buffer), "%s%c%s%c%s%c%d%c",
+ "testing", '\0', "testing", '\0',
+ "count", '\0', i, '\0');
+ ASSERT_EQ(0, rados_service_update_status(cluster, buffer));
+ sleep(1);
+ }
+ rados_shutdown(cluster);
+}
diff --git a/src/test/librados/service_cxx.cc b/src/test/librados/service_cxx.cc
new file mode 100644
index 000000000..1bf682af8
--- /dev/null
+++ b/src/test/librados/service_cxx.cc
@@ -0,0 +1,105 @@
+#include <algorithm>
+#include <thread>
+#include <errno.h>
+#include "gtest/gtest.h"
+
+#include "include/rados/librados.hpp"
+#include "include/stringify.h"
+#include "common/config_proxy.h"
+#include "test/librados/test_cxx.h"
+#include "test/librados/testcase_cxx.h"
+#include "test/unit.cc"
+
+using namespace std;
+using namespace librados;
+
+TEST(LibRadosServicePP, RegisterEarly) {
+ Rados cluster;
+ cluster.init("admin");
+ ASSERT_EQ(0, cluster.conf_read_file(NULL));
+ cluster.conf_parse_env(NULL);
+ string name = string("pid") + stringify(getpid());
+ ASSERT_EQ(0, cluster.service_daemon_register(
+ "laundry", name, {{"foo", "bar"}, {"this", "that"}}));
+ ASSERT_EQ(-EEXIST, cluster.service_daemon_register(
+ "laundry", name, {{"foo", "bar"}, {"this", "that"}}));
+ ASSERT_EQ(0, cluster.connect());
+ sleep(5);
+ cluster.shutdown();
+}
+
+TEST(LibRadosServicePP, RegisterLate) {
+ Rados cluster;
+ cluster.init("admin");
+ ASSERT_EQ(0, cluster.conf_read_file(NULL));
+ cluster.conf_parse_env(NULL);
+ ASSERT_EQ("", connect_cluster_pp(cluster));
+ string name = string("pid") + stringify(getpid());
+ ASSERT_EQ(0, cluster.service_daemon_register(
+ "laundry", name, {{"foo", "bar"}, {"this", "that"}}));
+}
+
+TEST(LibRadosServicePP, Status) {
+ Rados cluster;
+ cluster.init("admin");
+ ASSERT_EQ(0, cluster.conf_read_file(NULL));
+ cluster.conf_parse_env(NULL);
+ string name = string("pid") + stringify(getpid());
+ ASSERT_EQ(-ENOTCONN, cluster.service_daemon_update_status(
+ {{"testing", "starting"}}));
+ ASSERT_EQ(0, cluster.connect());
+ ASSERT_EQ(0, cluster.service_daemon_register(
+ "laundry", name, {{"foo", "bar"}, {"this", "that"}}));
+ for (int i=0; i<20; ++i) {
+ ASSERT_EQ(0, cluster.service_daemon_update_status({
+ {"testing", "running"},
+ {"count", stringify(i)}
+ }));
+ sleep(1);
+ }
+ cluster.shutdown();
+}
+
+TEST(LibRadosServicePP, Close) {
+ int tries = 20;
+ string name = string("close-test-pid") + stringify(getpid());
+ int i;
+ for (i = 0; i < tries; ++i) {
+ cout << "attempt " << i << " of " << tries << std::endl;
+ {
+ Rados cluster;
+ cluster.init("admin");
+ ASSERT_EQ(0, cluster.conf_read_file(NULL));
+ cluster.conf_parse_env(NULL);
+ ASSERT_EQ(0, cluster.connect());
+ ASSERT_EQ(0, cluster.service_daemon_register(
+ "laundry", name, {{"foo", "bar"}, {"this", "that"}}));
+ sleep(3); // let it register
+ cluster.shutdown();
+ }
+ // mgr updates servicemap every tick
+ //sleep(g_conf().get_val<int64_t>("mgr_tick_period"));
+ std::this_thread::sleep_for(g_conf().get_val<std::chrono::seconds>(
+ "mgr_tick_period"));
+ // make sure we are deregistered
+ {
+ Rados cluster;
+ cluster.init("admin");
+ ASSERT_EQ(0, cluster.conf_read_file(NULL));
+ cluster.conf_parse_env(NULL);
+ ASSERT_EQ(0, cluster.connect());
+ bufferlist inbl, outbl;
+ ASSERT_EQ(0, cluster.mon_command("{\"prefix\": \"service dump\"}",
+ inbl, &outbl, NULL));
+ string s = outbl.to_str();
+ cluster.shutdown();
+
+ if (s.find(name) != string::npos) {
+ cout << " failed to deregister:\n" << s << std::endl;
+ } else {
+ break;
+ }
+ }
+ }
+ ASSERT_LT(i, tries);
+}
diff --git a/src/test/librados/snapshots.cc b/src/test/librados/snapshots.cc
new file mode 100644
index 000000000..384447ca2
--- /dev/null
+++ b/src/test/librados/snapshots.cc
@@ -0,0 +1,356 @@
+#include "include/rados.h"
+#include "test/librados/test.h"
+#include "test/librados/TestCase.h"
+#include "crimson_utils.h"
+
+#include <algorithm>
+#include <errno.h>
+#include "gtest/gtest.h"
+#include <string>
+
+using std::string;
+
+typedef RadosTest LibRadosSnapshots;
+typedef RadosTest LibRadosSnapshotsSelfManaged;
+typedef RadosTestEC LibRadosSnapshotsEC;
+typedef RadosTestEC LibRadosSnapshotsSelfManagedEC;
+
+const int bufsize = 128;
+
+TEST_F(LibRadosSnapshots, SnapList) {
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_ioctx_snap_create(ioctx, "snap1"));
+ rados_snap_t snaps[10];
+ EXPECT_EQ(1, rados_ioctx_snap_list(ioctx, snaps,
+ sizeof(snaps) / sizeof(snaps[0])));
+ rados_snap_t rid;
+ EXPECT_EQ(0, rados_ioctx_snap_lookup(ioctx, "snap1", &rid));
+ EXPECT_EQ(rid, snaps[0]);
+ EXPECT_EQ(0, rados_ioctx_snap_remove(ioctx, "snap1"));
+}
+
+TEST_F(LibRadosSnapshots, SnapRemove) {
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_ioctx_snap_create(ioctx, "snap1"));
+ rados_snap_t rid;
+ ASSERT_EQ(0, rados_ioctx_snap_lookup(ioctx, "snap1", &rid));
+ ASSERT_EQ(-EEXIST, rados_ioctx_snap_create(ioctx, "snap1"));
+ ASSERT_EQ(0, rados_ioctx_snap_remove(ioctx, "snap1"));
+ ASSERT_EQ(-ENOENT, rados_ioctx_snap_lookup(ioctx, "snap1", &rid));
+}
+
+TEST_F(LibRadosSnapshots, Rollback) {
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_ioctx_snap_create(ioctx, "snap1"));
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ EXPECT_EQ(0, rados_write_full(ioctx, "foo", buf2, sizeof(buf2)));
+ EXPECT_EQ(0, rados_ioctx_snap_rollback(ioctx, "foo", "snap1"));
+ char buf3[sizeof(buf)];
+ EXPECT_EQ((int)sizeof(buf3), rados_read(ioctx, "foo", buf3, sizeof(buf3), 0));
+ EXPECT_EQ(0, memcmp(buf, buf3, sizeof(buf)));
+ EXPECT_EQ(0, rados_ioctx_snap_remove(ioctx, "snap1"));
+}
+
+TEST_F(LibRadosSnapshots, SnapGetName) {
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_ioctx_snap_create(ioctx, "snapfoo"));
+ rados_snap_t rid;
+ EXPECT_EQ(0, rados_ioctx_snap_lookup(ioctx, "snapfoo", &rid));
+ EXPECT_EQ(-ENOENT, rados_ioctx_snap_lookup(ioctx, "snapbar", &rid));
+ char name[128];
+ memset(name, 0, sizeof(name));
+ EXPECT_EQ(0, rados_ioctx_snap_get_name(ioctx, rid, name, sizeof(name)));
+ time_t snaptime;
+ EXPECT_EQ(0, rados_ioctx_snap_get_stamp(ioctx, rid, &snaptime));
+ EXPECT_EQ(0, strcmp(name, "snapfoo"));
+ EXPECT_EQ(0, rados_ioctx_snap_remove(ioctx, "snapfoo"));
+}
+
+TEST_F(LibRadosSnapshotsSelfManaged, Snap) {
+ std::vector<uint64_t> my_snaps;
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+
+ my_snaps.push_back(-2);
+ rados_completion_t completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr,
+ &completion));
+ rados_aio_ioctx_selfmanaged_snap_create(ioctx, &my_snaps.back(), completion);
+ ASSERT_EQ(0, rados_aio_wait_for_complete(completion));
+ rados_aio_release(completion);
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf2, sizeof(buf2), 0));
+ rados_ioctx_snap_set_read(ioctx, my_snaps[1]-1);
+ char buf3[sizeof(buf)];
+ ASSERT_EQ(-ENOENT, rados_read(ioctx, "foo", buf3, sizeof(buf3), 0));
+
+ rados_ioctx_snap_set_read(ioctx, my_snaps[1]);
+ ASSERT_EQ((int)sizeof(buf3), rados_read(ioctx, "foo", buf3, sizeof(buf3), 0));
+ ASSERT_EQ(0, memcmp(buf3, buf, sizeof(buf)));
+
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr,
+ &completion));
+ rados_aio_ioctx_selfmanaged_snap_remove(ioctx, my_snaps.back(), completion);
+ ASSERT_EQ(0, rados_aio_wait_for_complete(completion));
+ rados_aio_release(completion);
+ my_snaps.pop_back();
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps.back()));
+ my_snaps.pop_back();
+ rados_ioctx_snap_set_read(ioctx, LIBRADOS_SNAP_HEAD);
+ ASSERT_EQ(0, rados_remove(ioctx, "foo"));
+}
+
+TEST_F(LibRadosSnapshotsSelfManaged, Rollback) {
+ std::vector<uint64_t> my_snaps;
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ // First write
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ // Second write
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf2, sizeof(buf2), 0));
+ // Rollback to my_snaps[1] - Object is expeceted to conatin the first write
+ rados_ioctx_selfmanaged_snap_rollback(ioctx, "foo", my_snaps[1]);
+ char buf3[sizeof(buf)];
+ ASSERT_EQ((int)sizeof(buf3), rados_read(ioctx, "foo", buf3, sizeof(buf3), 0));
+ ASSERT_EQ(0, memcmp(buf3, buf, sizeof(buf)));
+
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, rados_remove(ioctx, "foo"));
+}
+
+TEST_F(LibRadosSnapshotsSelfManaged, FutureSnapRollback) {
+ std::vector<uint64_t> my_snaps;
+ // Snapshot 1
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ // First write
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+
+ // Snapshot 2
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ // Second write
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf2, sizeof(buf2), 0));
+ // Snapshot 3
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps.back()));
+
+ // Rollback to the last snap id - Object is expected to conatin
+ // latest write (head object)
+ rados_ioctx_selfmanaged_snap_rollback(ioctx, "foo", my_snaps[2]);
+ char buf3[sizeof(buf)];
+ ASSERT_EQ((int)sizeof(buf3), rados_read(ioctx, "foo", buf3, sizeof(buf3), 0));
+ ASSERT_EQ(0, memcmp(buf3, buf2, sizeof(buf)));
+
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, rados_remove(ioctx, "foo"));
+}
+
+
+
+// EC testing
+TEST_F(LibRadosSnapshotsEC, SnapList) {
+ SKIP_IF_CRIMSON();
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_ioctx_snap_create(ioctx, "snap1"));
+ rados_snap_t snaps[10];
+ EXPECT_EQ(1, rados_ioctx_snap_list(ioctx, snaps,
+ sizeof(snaps) / sizeof(snaps[0])));
+ rados_snap_t rid;
+ EXPECT_EQ(0, rados_ioctx_snap_lookup(ioctx, "snap1", &rid));
+ EXPECT_EQ(rid, snaps[0]);
+ EXPECT_EQ(0, rados_ioctx_snap_remove(ioctx, "snap1"));
+}
+
+TEST_F(LibRadosSnapshotsEC, SnapRemove) {
+ SKIP_IF_CRIMSON();
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_ioctx_snap_create(ioctx, "snap1"));
+ rados_snap_t rid;
+ ASSERT_EQ(0, rados_ioctx_snap_lookup(ioctx, "snap1", &rid));
+ ASSERT_EQ(-EEXIST, rados_ioctx_snap_create(ioctx, "snap1"));
+ ASSERT_EQ(0, rados_ioctx_snap_remove(ioctx, "snap1"));
+ ASSERT_EQ(-ENOENT, rados_ioctx_snap_lookup(ioctx, "snap1", &rid));
+}
+
+TEST_F(LibRadosSnapshotsEC, Rollback) {
+ SKIP_IF_CRIMSON();
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_ioctx_snap_create(ioctx, "snap1"));
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ EXPECT_EQ(0, rados_write_full(ioctx, "foo", buf2, sizeof(buf2)));
+ EXPECT_EQ(0, rados_ioctx_snap_rollback(ioctx, "foo", "snap1"));
+ char buf3[sizeof(buf)];
+ EXPECT_EQ((int)sizeof(buf3), rados_read(ioctx, "foo", buf3, sizeof(buf3), 0));
+ EXPECT_EQ(0, memcmp(buf, buf3, sizeof(buf)));
+ EXPECT_EQ(0, rados_ioctx_snap_remove(ioctx, "snap1"));
+}
+
+TEST_F(LibRadosSnapshotsEC, SnapGetName) {
+ SKIP_IF_CRIMSON();
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_ioctx_snap_create(ioctx, "snapfoo"));
+ rados_snap_t rid;
+ EXPECT_EQ(0, rados_ioctx_snap_lookup(ioctx, "snapfoo", &rid));
+ EXPECT_EQ(-ENOENT, rados_ioctx_snap_lookup(ioctx, "snapbar", &rid));
+ char name[128];
+ memset(name, 0, sizeof(name));
+ EXPECT_EQ(0, rados_ioctx_snap_get_name(ioctx, rid, name, sizeof(name)));
+ time_t snaptime;
+ EXPECT_EQ(0, rados_ioctx_snap_get_stamp(ioctx, rid, &snaptime));
+ EXPECT_EQ(0, strcmp(name, "snapfoo"));
+ EXPECT_EQ(0, rados_ioctx_snap_remove(ioctx, "snapfoo"));
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedEC, Snap) {
+ SKIP_IF_CRIMSON();
+ std::vector<uint64_t> my_snaps;
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ int bsize = alignment;
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, bsize, 0));
+
+ my_snaps.push_back(-2);
+ rados_completion_t completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr,
+ &completion));
+ rados_aio_ioctx_selfmanaged_snap_create(ioctx, &my_snaps.back(), completion);
+ ASSERT_EQ(0, rados_aio_wait_for_complete(completion));
+ rados_aio_release(completion);
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char *buf2 = (char *)new char[bsize];
+ memset(buf2, 0xdd, bsize);
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf2, bsize, bsize));
+ rados_ioctx_snap_set_read(ioctx, my_snaps[1]-1);
+ char *buf3 = (char *)new char[bsize*2];
+ ASSERT_EQ(-ENOENT, rados_read(ioctx, "foo", buf3, bsize*2, 0));
+
+ rados_ioctx_snap_set_read(ioctx, my_snaps[1]);
+ ASSERT_EQ(bsize, rados_read(ioctx, "foo", buf3, bsize*2, 0));
+ ASSERT_EQ(0, memcmp(buf3, buf, bsize));
+
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr,
+ &completion));
+ rados_aio_ioctx_selfmanaged_snap_remove(ioctx, my_snaps.back(), completion);
+ ASSERT_EQ(0, rados_aio_wait_for_complete(completion));
+ rados_aio_release(completion);
+ my_snaps.pop_back();
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps.back()));
+ my_snaps.pop_back();
+ rados_ioctx_snap_set_read(ioctx, LIBRADOS_SNAP_HEAD);
+ ASSERT_EQ(0, rados_remove(ioctx, "foo"));
+ delete[] buf;
+ delete[] buf2;
+ delete[] buf3;
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedEC, Rollback) {
+ SKIP_IF_CRIMSON();
+ std::vector<uint64_t> my_snaps;
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ int bsize = alignment;
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, bsize, 0));
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char *buf2 = (char *)new char[bsize];
+ memset(buf2, 0xdd, bsize);
+
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf2, bsize, bsize));
+ rados_ioctx_selfmanaged_snap_rollback(ioctx, "foo", my_snaps[1]);
+ char *buf3 = (char *)new char[bsize*2];
+ ASSERT_EQ(bsize, rados_read(ioctx, "foo", buf3, bsize*2, 0));
+ ASSERT_EQ(0, memcmp(buf3, buf, bsize));
+
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, rados_remove(ioctx, "foo"));
+ delete[] buf;
+ delete[] buf2;
+ delete[] buf3;
+}
diff --git a/src/test/librados/snapshots_cxx.cc b/src/test/librados/snapshots_cxx.cc
new file mode 100644
index 000000000..8098b2cb7
--- /dev/null
+++ b/src/test/librados/snapshots_cxx.cc
@@ -0,0 +1,790 @@
+#include <algorithm>
+#include <errno.h>
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "include/rados.h"
+#include "include/rados/librados.hpp"
+#include "test/librados/test_cxx.h"
+#include "test/librados/testcase_cxx.h"
+#include "crimson_utils.h"
+
+using namespace librados;
+
+typedef RadosTestPP LibRadosSnapshotsPP;
+typedef RadosTestPP LibRadosSnapshotsSelfManagedPP;
+typedef RadosTestECPP LibRadosSnapshotsECPP;
+typedef RadosTestECPP LibRadosSnapshotsSelfManagedECPP;
+
+const int bufsize = 128;
+
+TEST_F(LibRadosSnapshotsPP, SnapListPP) {
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ ASSERT_FALSE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name));
+ ASSERT_EQ(0, ioctx.snap_create("snap1"));
+ ASSERT_FALSE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name));
+ std::vector<snap_t> snaps;
+ EXPECT_EQ(0, ioctx.snap_list(&snaps));
+ EXPECT_EQ(1U, snaps.size());
+ snap_t rid;
+ EXPECT_EQ(0, ioctx.snap_lookup("snap1", &rid));
+ EXPECT_EQ(rid, snaps[0]);
+ EXPECT_EQ(0, ioctx.snap_remove("snap1"));
+ ASSERT_FALSE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name));
+}
+
+TEST_F(LibRadosSnapshotsPP, SnapRemovePP) {
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.snap_create("snap1"));
+ rados_snap_t rid;
+ ASSERT_EQ(0, ioctx.snap_lookup("snap1", &rid));
+ ASSERT_EQ(0, ioctx.snap_remove("snap1"));
+ ASSERT_EQ(-ENOENT, ioctx.snap_lookup("snap1", &rid));
+}
+
+TEST_F(LibRadosSnapshotsPP, RollbackPP) {
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.snap_create("snap1"));
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ EXPECT_EQ(0, ioctx.write_full("foo", bl2));
+ EXPECT_EQ(0, ioctx.snap_rollback("foo", "snap1"));
+ bufferlist bl3;
+ EXPECT_EQ((int)sizeof(buf), ioctx.read("foo", bl3, sizeof(buf), 0));
+ EXPECT_EQ(0, memcmp(buf, bl3.c_str(), sizeof(buf)));
+ EXPECT_EQ(0, ioctx.snap_remove("snap1"));
+}
+
+TEST_F(LibRadosSnapshotsPP, SnapGetNamePP) {
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.snap_create("snapfoo"));
+ rados_snap_t rid;
+ EXPECT_EQ(0, ioctx.snap_lookup("snapfoo", &rid));
+ EXPECT_EQ(-ENOENT, ioctx.snap_lookup("snapbar", &rid));
+ std::string name;
+ EXPECT_EQ(0, ioctx.snap_get_name(rid, &name));
+ time_t snaptime;
+ EXPECT_EQ(0, ioctx.snap_get_stamp(rid, &snaptime));
+ EXPECT_EQ(0, strcmp(name.c_str(), "snapfoo"));
+ EXPECT_EQ(0, ioctx.snap_remove("snapfoo"));
+}
+
+TEST_F(LibRadosSnapshotsPP, SnapCreateRemovePP) {
+ // reproduces http://tracker.ceph.com/issues/10262
+ bufferlist bl;
+ bl.append("foo");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ ASSERT_EQ(0, ioctx.snap_create("snapfoo"));
+ ASSERT_EQ(0, ioctx.remove("foo"));
+ ASSERT_EQ(0, ioctx.snap_create("snapbar"));
+
+ std::unique_ptr<librados::ObjectWriteOperation> op(new librados::ObjectWriteOperation());
+ op->create(false);
+ op->remove();
+ ASSERT_EQ(0, ioctx.operate("foo", op.get()));
+
+ EXPECT_EQ(0, ioctx.snap_remove("snapfoo"));
+ EXPECT_EQ(0, ioctx.snap_remove("snapbar"));
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedPP, SnapPP) {
+ std::vector<uint64_t> my_snaps;
+ my_snaps.push_back(-2);
+ ASSERT_FALSE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ASSERT_TRUE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+
+ my_snaps.push_back(-2);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ioctx.aio_selfmanaged_snap_create(&my_snaps.back(), completion);
+ ASSERT_EQ(0, completion->wait_for_complete());
+ completion->release();
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), 0));
+
+ ioctx.snap_set_read(my_snaps[1]);
+ bufferlist bl3;
+ ASSERT_EQ((int)sizeof(buf), ioctx.read("foo", bl3, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, sizeof(buf)));
+
+ completion = cluster.aio_create_completion();
+ ioctx.aio_selfmanaged_snap_remove(my_snaps.back(), completion);
+ ASSERT_EQ(0, completion->wait_for_complete());
+ completion->release();
+ my_snaps.pop_back();
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ ioctx.snap_set_read(LIBRADOS_SNAP_HEAD);
+ ASSERT_TRUE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name));
+ ASSERT_EQ(0, ioctx.remove("foo"));
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedPP, RollbackPP) {
+ SKIP_IF_CRIMSON();
+ std::vector<uint64_t> my_snaps;
+ IoCtx readioctx;
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), readioctx));
+ readioctx.set_namespace(nspace);
+ readioctx.snap_set_read(LIBRADOS_SNAP_DIR);
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ //Write 3 consecutive buffers
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), bufsize));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), bufsize*2));
+
+ snap_set_t ss;
+
+ snap_t head = SNAP_HEAD;
+ ASSERT_EQ(0, readioctx.list_snaps("foo", &ss));
+ ASSERT_EQ(1u, ss.clones.size());
+ ASSERT_EQ(head, ss.clones[0].cloneid);
+ ASSERT_EQ(0u, ss.clones[0].snaps.size());
+ ASSERT_EQ(0u, ss.clones[0].overlap.size());
+ ASSERT_EQ(384u, ss.clones[0].size);
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ //Change the middle buffer
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), bufsize));
+ //Add another after
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), bufsize*3));
+
+ ASSERT_EQ(-EINVAL, ioctx.list_snaps("foo", &ss));
+ ObjectReadOperation o;
+ o.list_snaps(&ss, NULL);
+ ASSERT_EQ(-EINVAL, ioctx.operate("foo", &o, NULL));
+
+ ASSERT_EQ(0, readioctx.list_snaps("foo", &ss));
+ ASSERT_EQ(2u, ss.clones.size());
+ ASSERT_EQ(my_snaps[1], ss.clones[0].cloneid);
+ ASSERT_EQ(1u, ss.clones[0].snaps.size());
+ ASSERT_EQ(my_snaps[1], ss.clones[0].snaps[0]);
+ ASSERT_EQ(2u, ss.clones[0].overlap.size());
+ ASSERT_EQ(0u, ss.clones[0].overlap[0].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[0].second);
+ ASSERT_EQ(256u, ss.clones[0].overlap[1].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[1].second);
+ ASSERT_EQ(384u, ss.clones[0].size);
+ ASSERT_EQ(head, ss.clones[1].cloneid);
+ ASSERT_EQ(0u, ss.clones[1].snaps.size());
+ ASSERT_EQ(0u, ss.clones[1].overlap.size());
+ ASSERT_EQ(512u, ss.clones[1].size);
+
+ ioctx.selfmanaged_snap_rollback("foo", my_snaps[1]);
+
+ bufferlist bl3;
+ ASSERT_EQ((int)sizeof(buf), ioctx.read("foo", bl3, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ((int)sizeof(buf), ioctx.read("foo", bl3, sizeof(buf), bufsize));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ((int)sizeof(buf), ioctx.read("foo", bl3, sizeof(buf), bufsize*2));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ((int)0, ioctx.read("foo", bl3, sizeof(buf), bufsize*3));
+
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ readioctx.close();
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedPP, SnapOverlapPP) {
+ // WIP https://tracker.ceph.com/issues/58263
+ SKIP_IF_CRIMSON();
+ std::vector<uint64_t> my_snaps;
+ IoCtx readioctx;
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), readioctx));
+ readioctx.set_namespace(nspace);
+ readioctx.snap_set_read(LIBRADOS_SNAP_DIR);
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), bufsize*2));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), bufsize*4));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), bufsize*6));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), bufsize*8));
+
+ snap_set_t ss;
+ snap_t head = SNAP_HEAD;
+ ASSERT_EQ(0, readioctx.list_snaps("foo", &ss));
+ ASSERT_EQ(1u, ss.clones.size());
+ ASSERT_EQ(head, ss.clones[0].cloneid);
+ ASSERT_EQ(0u, ss.clones[0].snaps.size());
+ ASSERT_EQ(0u, ss.clones[0].overlap.size());
+ ASSERT_EQ(1152u, ss.clones[0].size);
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), bufsize*1));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), bufsize*3));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), bufsize*5));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), bufsize*7));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), bufsize*9));
+
+ ASSERT_EQ(0, readioctx.list_snaps("foo", &ss));
+ ASSERT_EQ(2u, ss.clones.size());
+ ASSERT_EQ(my_snaps[1], ss.clones[0].cloneid);
+ ASSERT_EQ(1u, ss.clones[0].snaps.size());
+ ASSERT_EQ(my_snaps[1], ss.clones[0].snaps[0]);
+ ASSERT_EQ(5u, ss.clones[0].overlap.size());
+ ASSERT_EQ(0u, ss.clones[0].overlap[0].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[0].second);
+ ASSERT_EQ(256u, ss.clones[0].overlap[1].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[1].second);
+ ASSERT_EQ(512u, ss.clones[0].overlap[2].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[2].second);
+ ASSERT_EQ(768u, ss.clones[0].overlap[3].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[3].second);
+ ASSERT_EQ(1024u, ss.clones[0].overlap[4].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[4].second);
+ ASSERT_EQ(1152u, ss.clones[0].size);
+ ASSERT_EQ(head, ss.clones[1].cloneid);
+ ASSERT_EQ(0u, ss.clones[1].snaps.size());
+ ASSERT_EQ(0u, ss.clones[1].overlap.size());
+ ASSERT_EQ(1280u, ss.clones[1].size);
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+
+ char buf3[sizeof(buf)];
+ memset(buf3, 0xee, sizeof(buf3));
+ bufferlist bl4;
+ bl4.append(buf3, sizeof(buf3));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf3), bufsize*1));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf3), bufsize*4));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf3), bufsize*5));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf3), bufsize*8));
+
+ ASSERT_EQ(0, readioctx.list_snaps("foo", &ss));
+ ASSERT_EQ(3u, ss.clones.size());
+ ASSERT_EQ(my_snaps[1], ss.clones[0].cloneid);
+ ASSERT_EQ(1u, ss.clones[0].snaps.size());
+ ASSERT_EQ(my_snaps[1], ss.clones[0].snaps[0]);
+ ASSERT_EQ(5u, ss.clones[0].overlap.size());
+ ASSERT_EQ(0u, ss.clones[0].overlap[0].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[0].second);
+ ASSERT_EQ(256u, ss.clones[0].overlap[1].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[1].second);
+ ASSERT_EQ(512u, ss.clones[0].overlap[2].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[2].second);
+ ASSERT_EQ(768u, ss.clones[0].overlap[3].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[3].second);
+ ASSERT_EQ(1024u, ss.clones[0].overlap[4].first);
+ ASSERT_EQ(128u, ss.clones[0].overlap[4].second);
+ ASSERT_EQ(1152u, ss.clones[0].size);
+
+ ASSERT_EQ(my_snaps[2], ss.clones[1].cloneid);
+ ASSERT_EQ(1u, ss.clones[1].snaps.size());
+ ASSERT_EQ(my_snaps[2], ss.clones[1].snaps[0]);
+ ASSERT_EQ(4u, ss.clones[1].overlap.size());
+ ASSERT_EQ(0u, ss.clones[1].overlap[0].first);
+ ASSERT_EQ(128u, ss.clones[1].overlap[0].second);
+ ASSERT_EQ(256u, ss.clones[1].overlap[1].first);
+ ASSERT_EQ(256u, ss.clones[1].overlap[1].second);
+ ASSERT_EQ(768u, ss.clones[1].overlap[2].first);
+ ASSERT_EQ(256u, ss.clones[1].overlap[2].second);
+ ASSERT_EQ(1152u, ss.clones[1].overlap[3].first);
+ ASSERT_EQ(128u, ss.clones[1].overlap[3].second);
+ ASSERT_EQ(1280u, ss.clones[1].size);
+
+ ASSERT_EQ(head, ss.clones[2].cloneid);
+ ASSERT_EQ(0u, ss.clones[2].snaps.size());
+ ASSERT_EQ(0u, ss.clones[2].overlap.size());
+ ASSERT_EQ(1280u, ss.clones[2].size);
+
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ readioctx.close();
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedPP, Bug11677) {
+ std::vector<uint64_t> my_snaps;
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+
+ int bsize = 1<<20;
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ bufferlist bl1;
+ bl1.append(buf, bsize);
+ ASSERT_EQ(0, ioctx.write("foo", bl1, bsize, 0));
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+
+ std::unique_ptr<librados::ObjectWriteOperation> op(new librados::ObjectWriteOperation());
+ op->assert_exists();
+ op->remove();
+ ASSERT_EQ(0, ioctx.operate("foo", op.get()));
+
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ ioctx.snap_set_read(LIBRADOS_SNAP_HEAD);
+ delete[] buf;
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedPP, OrderSnap) {
+ std::vector<uint64_t> my_snaps;
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ int flags = librados::OPERATION_ORDERSNAP;
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ObjectWriteOperation op1;
+ op1.write(0, bl);
+ librados::AioCompletion *comp1 = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", comp1, &op1, flags));
+ ASSERT_EQ(0, comp1->wait_for_complete());
+ ASSERT_EQ(0, comp1->get_return_value());
+ comp1->release();
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ObjectWriteOperation op2;
+ op2.write(0, bl);
+ librados::AioCompletion *comp2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", comp2, &op2, flags));
+ ASSERT_EQ(0, comp2->wait_for_complete());
+ ASSERT_EQ(0, comp2->get_return_value());
+ comp2->release();
+
+ my_snaps.pop_back();
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ObjectWriteOperation op3;
+ op3.write(0, bl);
+ librados::AioCompletion *comp3 = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", comp3, &op3, flags));
+ ASSERT_EQ(0, comp3->wait_for_complete());
+ ASSERT_EQ(-EOLDSNAPC, comp3->get_return_value());
+ comp3->release();
+
+ ObjectWriteOperation op4;
+ op4.write(0, bl);
+ librados::AioCompletion *comp4 = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", comp4, &op4, 0));
+ ASSERT_EQ(0, comp4->wait_for_complete());
+ ASSERT_EQ(0, comp4->get_return_value());
+ comp4->release();
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedPP, WriteRollback) {
+ // https://tracker.ceph.com/issues/59114
+ GTEST_SKIP();
+ uint64_t snapid = 5;
+
+ // buf1
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+
+ // buf2
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+
+ // First write
+ ObjectWriteOperation op_write1;
+ op_write1.write(0, bl);
+ // Operate
+ librados::AioCompletion *comp_write = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", comp_write, &op_write1, 0));
+ ASSERT_EQ(0, comp_write->wait_for_complete());
+ ASSERT_EQ(0, comp_write->get_return_value());
+ comp_write->release();
+
+ // Take Snapshot
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&snapid));
+
+ // Rollback + Second write in the same op
+ ObjectWriteOperation op_write2_snap_rollback;
+ op_write2_snap_rollback.write(0, bl2);
+ op_write2_snap_rollback.selfmanaged_snap_rollback(snapid);
+ // Operate
+ librados::AioCompletion *comp_write2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", comp_write2, &op_write2_snap_rollback, 0));
+ ASSERT_EQ(0, comp_write2->wait_for_complete());
+ ASSERT_EQ(0, comp_write2->get_return_value());
+ comp_write2->release();
+
+ // Resolved should be first write
+ bufferlist bl3;
+ EXPECT_EQ((int)sizeof(buf), ioctx.read("foo", bl3, sizeof(buf), 0));
+ EXPECT_EQ(0, memcmp(buf, bl3.c_str(), sizeof(buf)));
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedPP, ReusePurgedSnap) {
+ std::vector<uint64_t> my_snaps;
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ASSERT_TRUE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+
+ my_snaps.push_back(-2);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ioctx.aio_selfmanaged_snap_create(&my_snaps.back(), completion);
+ ASSERT_EQ(0, completion->wait_for_complete());
+ completion->release();
+
+ std::cout << "deleting snap " << my_snaps.back() << " in pool "
+ << ioctx.get_pool_name() << std::endl;
+ completion = cluster.aio_create_completion();
+ ioctx.aio_selfmanaged_snap_remove(my_snaps.back(), completion);
+ ASSERT_EQ(0, completion->wait_for_complete());
+ completion->release();
+
+ std::cout << "waiting for snaps to purge" << std::endl;
+ sleep(15);
+
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), 0));
+
+ // scrub it out?
+ //sleep(600);
+}
+
+// EC testing
+TEST_F(LibRadosSnapshotsECPP, SnapListPP) {
+ SKIP_IF_CRIMSON();
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.snap_create("snap1"));
+ std::vector<snap_t> snaps;
+ EXPECT_EQ(0, ioctx.snap_list(&snaps));
+ EXPECT_EQ(1U, snaps.size());
+ snap_t rid;
+ EXPECT_EQ(0, ioctx.snap_lookup("snap1", &rid));
+ EXPECT_EQ(rid, snaps[0]);
+ EXPECT_EQ(0, ioctx.snap_remove("snap1"));
+}
+
+TEST_F(LibRadosSnapshotsECPP, SnapRemovePP) {
+ SKIP_IF_CRIMSON();
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.snap_create("snap1"));
+ rados_snap_t rid;
+ ASSERT_EQ(0, ioctx.snap_lookup("snap1", &rid));
+ ASSERT_EQ(0, ioctx.snap_remove("snap1"));
+ ASSERT_EQ(-ENOENT, ioctx.snap_lookup("snap1", &rid));
+}
+
+TEST_F(LibRadosSnapshotsECPP, RollbackPP) {
+ SKIP_IF_CRIMSON();
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.snap_create("snap1"));
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ EXPECT_EQ(0, ioctx.write_full("foo", bl2));
+ EXPECT_EQ(0, ioctx.snap_rollback("foo", "snap1"));
+ bufferlist bl3;
+ EXPECT_EQ((int)sizeof(buf), ioctx.read("foo", bl3, sizeof(buf), 0));
+ EXPECT_EQ(0, memcmp(buf, bl3.c_str(), sizeof(buf)));
+ EXPECT_EQ(0, ioctx.snap_remove("snap1"));
+}
+
+TEST_F(LibRadosSnapshotsECPP, SnapGetNamePP) {
+ SKIP_IF_CRIMSON();
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.snap_create("snapfoo"));
+ rados_snap_t rid;
+ EXPECT_EQ(0, ioctx.snap_lookup("snapfoo", &rid));
+ EXPECT_EQ(-ENOENT, ioctx.snap_lookup("snapbar", &rid));
+ std::string name;
+ EXPECT_EQ(0, ioctx.snap_get_name(rid, &name));
+ time_t snaptime;
+ EXPECT_EQ(0, ioctx.snap_get_stamp(rid, &snaptime));
+ EXPECT_EQ(0, strcmp(name.c_str(), "snapfoo"));
+ EXPECT_EQ(0, ioctx.snap_remove("snapfoo"));
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedECPP, SnapPP) {
+ SKIP_IF_CRIMSON();
+ std::vector<uint64_t> my_snaps;
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ int bsize = alignment;
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ bufferlist bl1;
+ bl1.append(buf, bsize);
+ ASSERT_EQ(0, ioctx.write("foo", bl1, bsize, 0));
+
+ my_snaps.push_back(-2);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ioctx.aio_selfmanaged_snap_create(&my_snaps.back(), completion);
+ ASSERT_EQ(0, completion->wait_for_complete());
+ completion->release();
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char *buf2 = (char *)new char[bsize];
+ memset(buf2, 0xdd, bsize);
+ bufferlist bl2;
+ bl2.append(buf2, bsize);
+ // Add another aligned buffer
+ ASSERT_EQ(0, ioctx.write("foo", bl2, bsize, bsize));
+
+ ioctx.snap_set_read(my_snaps[1]);
+ bufferlist bl3;
+ ASSERT_EQ(bsize, ioctx.read("foo", bl3, bsize*3, 0));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, bsize));
+
+ completion = cluster.aio_create_completion();
+ ioctx.aio_selfmanaged_snap_remove(my_snaps.back(), completion);
+ ASSERT_EQ(0, completion->wait_for_complete());
+ completion->release();
+ my_snaps.pop_back();
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ ioctx.snap_set_read(LIBRADOS_SNAP_HEAD);
+ ASSERT_EQ(0, ioctx.remove("foo"));
+ delete[] buf;
+ delete[] buf2;
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedECPP, RollbackPP) {
+ SKIP_IF_CRIMSON();
+ std::vector<uint64_t> my_snaps;
+ IoCtx readioctx;
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), readioctx));
+ readioctx.set_namespace(nspace);
+ readioctx.snap_set_read(LIBRADOS_SNAP_DIR);
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ int bsize = alignment;
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ bufferlist bl1;
+ bl1.append(buf, bsize);
+ //Write 3 consecutive buffers
+ ASSERT_EQ(0, ioctx.write("foo", bl1, bsize, 0));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, bsize, bsize));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, bsize, bsize*2));
+
+ snap_set_t ss;
+
+ snap_t head = SNAP_HEAD;
+ ASSERT_EQ(0, readioctx.list_snaps("foo", &ss));
+ ASSERT_EQ(1u, ss.clones.size());
+ ASSERT_EQ(head, ss.clones[0].cloneid);
+ ASSERT_EQ(0u, ss.clones[0].snaps.size());
+ ASSERT_EQ(0u, ss.clones[0].overlap.size());
+ ASSERT_EQ((unsigned)(bsize*3), ss.clones[0].size);
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ char *buf2 = (char *)new char[bsize];
+ memset(buf2, 0xdd, bsize);
+ bufferlist bl2;
+ bl2.append(buf2, bsize);
+ //Change the middle buffer
+ //ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), bufsize));
+ //Add another after
+ ASSERT_EQ(0, ioctx.write("foo", bl2, bsize, bsize*3));
+
+ ASSERT_EQ(-EINVAL, ioctx.list_snaps("foo", &ss));
+ ObjectReadOperation o;
+ o.list_snaps(&ss, NULL);
+ ASSERT_EQ(-EINVAL, ioctx.operate("foo", &o, NULL));
+
+ ASSERT_EQ(0, readioctx.list_snaps("foo", &ss));
+ ASSERT_EQ(2u, ss.clones.size());
+ ASSERT_EQ(my_snaps[1], ss.clones[0].cloneid);
+ ASSERT_EQ(1u, ss.clones[0].snaps.size());
+ ASSERT_EQ(my_snaps[1], ss.clones[0].snaps[0]);
+ ASSERT_EQ(1u, ss.clones[0].overlap.size());
+ ASSERT_EQ(0u, ss.clones[0].overlap[0].first);
+ ASSERT_EQ((unsigned)bsize*3, ss.clones[0].overlap[0].second);
+ ASSERT_EQ((unsigned)bsize*3, ss.clones[0].size);
+ ASSERT_EQ(head, ss.clones[1].cloneid);
+ ASSERT_EQ(0u, ss.clones[1].snaps.size());
+ ASSERT_EQ(0u, ss.clones[1].overlap.size());
+ ASSERT_EQ((unsigned)bsize*4, ss.clones[1].size);
+
+ ioctx.selfmanaged_snap_rollback("foo", my_snaps[1]);
+
+ bufferlist bl3;
+ ASSERT_EQ(bsize, ioctx.read("foo", bl3, bsize, 0));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, bsize));
+ ASSERT_EQ(bsize, ioctx.read("foo", bl3, bsize, bsize));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, bsize));
+ ASSERT_EQ(bsize, ioctx.read("foo", bl3, bsize, bsize*2));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, bsize));
+ ASSERT_EQ(0, ioctx.read("foo", bl3, bsize, bsize*3));
+
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ readioctx.close();
+
+ delete[] buf;
+ delete[] buf2;
+}
+
+TEST_F(LibRadosSnapshotsSelfManagedECPP, Bug11677) {
+ SKIP_IF_CRIMSON();
+ std::vector<uint64_t> my_snaps;
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+
+ int bsize = alignment;
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ bufferlist bl1;
+ bl1.append(buf, bsize);
+ ASSERT_EQ(0, ioctx.write("foo", bl1, bsize, 0));
+
+ my_snaps.push_back(-2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back()));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ ::std::reverse(my_snaps.begin(), my_snaps.end());
+
+ std::unique_ptr<librados::ObjectWriteOperation> op(new librados::ObjectWriteOperation());
+ op->assert_exists();
+ op->remove();
+ ASSERT_EQ(0, ioctx.operate("foo", op.get()));
+
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back()));
+ my_snaps.pop_back();
+ ioctx.snap_set_read(LIBRADOS_SNAP_HEAD);
+ delete[] buf;
+}
diff --git a/src/test/librados/snapshots_stats.cc b/src/test/librados/snapshots_stats.cc
new file mode 100644
index 000000000..143299ab1
--- /dev/null
+++ b/src/test/librados/snapshots_stats.cc
@@ -0,0 +1,332 @@
+#include "include/rados.h"
+#include "json_spirit/json_spirit.h"
+#include "test/librados/test.h"
+#include "test/librados/TestCase.h"
+
+#include <algorithm>
+#include <errno.h>
+#include "gtest/gtest.h"
+#include <string>
+#include <vector>
+
+using std::string;
+
+class LibRadosSnapshotStatsSelfManaged : public RadosTest {
+public:
+ LibRadosSnapshotStatsSelfManaged() {};
+ ~LibRadosSnapshotStatsSelfManaged() override {};
+protected:
+ void SetUp() override {
+ // disable pg autoscaler for the tests
+ string c =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"off\""
+ "}";
+ char *cmd[1];
+ cmd[0] = (char *)c.c_str();
+ std::cout << "Setting pg_autoscaler to 'off'" << std::endl;
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL,
+ 0, NULL, 0));
+
+ // disable scrubs for the test
+ c = string("{\"prefix\": \"osd set\",\"key\":\"noscrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+ c = string("{\"prefix\": \"osd set\",\"key\":\"nodeep-scrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+
+ RadosTest::SetUp();
+ }
+
+ void TearDown() override {
+ // re-enable pg autoscaler
+ string c =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"on\""
+ "}";
+ char *cmd[1];
+ cmd[0] = (char *)c.c_str();
+ std::cout << "Setting pg_autoscaler to 'on'" << std::endl;
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL,
+ 0, NULL, 0));
+
+ // re-enable scrubs
+ c = string("{\"prefix\": \"osd unset\",\"key\":\"noscrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+ c = string("{\"prefix\": \"osd unset\",\"key\":\"nodeep-scrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+
+ RadosTest::TearDown();
+ }
+};
+
+class LibRadosSnapshotStatsSelfManagedEC : public RadosTestEC {
+public:
+ LibRadosSnapshotStatsSelfManagedEC() {};
+ ~LibRadosSnapshotStatsSelfManagedEC() override {};
+protected:
+ void SetUp() override {
+ // disable pg autoscaler for the tests
+ string c =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"off\""
+ "}";
+ char *cmd[1];
+ cmd[0] = (char *)c.c_str();
+ std::cout << "Setting pg_autoscaler to 'off'" << std::endl;
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL,
+ 0, NULL, 0));
+
+ // disable scrubs for the test
+ c = string("{\"prefix\": \"osd set\",\"key\":\"noscrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+ c = string("{\"prefix\": \"osd set\",\"key\":\"nodeep-scrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+
+ RadosTestEC::SetUp();
+ }
+
+ void TearDown() override {
+ // re-enable pg autoscaler
+ string c =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"on\""
+ "}";
+ char *cmd[1];
+ cmd[0] = (char *)c.c_str();
+ std::cout << "Setting pg_autoscaler to 'on'" << std::endl;
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL,
+ 0, NULL, 0));
+
+ // re-enable scrubs
+ c = string("{\"prefix\": \"osd unset\",\"key\":\"noscrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+ c = string("{\"prefix\": \"osd unset\",\"key\":\"nodeep-scrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+
+ RadosTestEC::TearDown();
+ }
+};
+
+void get_snaptrim_stats(json_spirit::Object& pg_dump,
+ int *objs_trimmed,
+ double *trim_duration) {
+ // pg_map
+ json_spirit::Object pgmap;
+ for (json_spirit::Object::size_type i = 0; i < pg_dump.size(); ++i) {
+ json_spirit::Pair& p = pg_dump[i];
+ if (p.name_ == "pg_map") {
+ pgmap = p.value_.get_obj();
+ break;
+ }
+ }
+
+ // pg_stats array
+ json_spirit::Array pgs;
+ for (json_spirit::Object::size_type i = 0; i < pgmap.size(); ++i) {
+ json_spirit::Pair& p = pgmap[i];
+ if (p.name_ == "pg_stats") {
+ pgs = p.value_.get_array();
+ break;
+ }
+ }
+
+ // snaptrim stats
+ for (json_spirit::Object::size_type j = 0; j < pgs.size(); ++j) {
+ json_spirit::Object& pg_stat = pgs[j].get_obj();
+ for(json_spirit::Object::size_type k = 0; k < pg_stat.size(); ++k) {
+ json_spirit::Pair& stats = pg_stat[k];
+ if (stats.name_ == "objects_trimmed") {
+ *objs_trimmed += stats.value_.get_int();
+ }
+ if (stats.name_ == "snaptrim_duration") {
+ *trim_duration += stats.value_.get_real();
+ }
+ }
+ }
+}
+
+const int bufsize = 128;
+
+TEST_F(LibRadosSnapshotStatsSelfManaged, SnaptrimStats) {
+ int num_objs = 10;
+
+ // create objects
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_write(ioctx, obj.c_str(), buf, sizeof(buf), 0));
+ }
+
+ std::vector<uint64_t> my_snaps;
+ for (int snap = 0; snap < 1; ++snap) {
+ // create a snapshot, clone
+ std::vector<uint64_t> ns(1);
+ ns.insert(ns.end(), my_snaps.begin(), my_snaps.end());
+ my_snaps.swap(ns);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps[0]));
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_write(ioctx, obj.c_str(), buf2, sizeof(buf2), 0));
+ }
+ }
+
+ // wait for maps to settle
+ ASSERT_EQ(0, rados_wait_for_latest_osdmap(cluster));
+
+ // remove snaps - should trigger snaptrim
+ rados_ioctx_snap_set_read(ioctx, LIBRADOS_SNAP_HEAD);
+ for (unsigned snap = 0; snap < my_snaps.size(); ++snap) {
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps[snap]));
+ }
+
+ // sleep for few secs for the trim stats to populate
+ std::cout << "Waiting for snaptrim stats to be generated" << std::endl;
+ sleep(30);
+
+ // Dump pg stats and determine if snaptrim stats are getting set
+ int objects_trimmed = 0;
+ double snaptrim_duration = 0.0;
+ int tries = 0;
+ do {
+ char *buf, *st;
+ size_t buflen, stlen;
+ string c = string("{\"prefix\": \"pg dump\",\"format\":\"json\"}");
+ const char *cmd = c.c_str();
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)&cmd, 1, "", 0,
+ &buf, &buflen, &st, &stlen));
+ string outstr(buf, buflen);
+ json_spirit::Value v;
+ ASSERT_NE(0, json_spirit::read(outstr, v)) << "unable to parse json."
+ << '\n' << outstr;
+
+ // pg dump object
+ json_spirit::Object& obj = v.get_obj();
+ get_snaptrim_stats(obj, &objects_trimmed, &snaptrim_duration);
+ if (objects_trimmed < num_objs) {
+ tries++;
+ objects_trimmed = 0;
+ std::cout << "Still waiting for all objects to be trimmed... " <<std::endl;
+ sleep(30);
+ }
+ } while(objects_trimmed < num_objs && tries < 5);
+
+ // final check for objects trimmed
+ ASSERT_EQ(objects_trimmed, num_objs);
+ std::cout << "Snaptrim duration: " << snaptrim_duration << std::endl;
+ ASSERT_GT(snaptrim_duration, 0.0);
+
+ // clean-up remaining objects
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_remove(ioctx, obj.c_str()));
+ }
+}
+
+// EC testing
+TEST_F(LibRadosSnapshotStatsSelfManagedEC, SnaptrimStats) {
+ int num_objs = 10;
+ int bsize = alignment;
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ // create objects
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_write(ioctx, obj.c_str(), buf, bsize, 0));
+ }
+
+ std::vector<uint64_t> my_snaps;
+ for (int snap = 0; snap < 1; ++snap) {
+ // create a snapshot, clone
+ std::vector<uint64_t> ns(1);
+ ns.insert(ns.end(), my_snaps.begin(), my_snaps.end());
+ my_snaps.swap(ns);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps[0]));
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ char *buf2 = (char *)new char[bsize];
+ memset(buf2, 0xdd, bsize);
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_write(ioctx, obj.c_str(), buf2, bsize, bsize));
+ }
+ delete[] buf2;
+ }
+
+ // wait for maps to settle
+ ASSERT_EQ(0, rados_wait_for_latest_osdmap(cluster));
+
+ // remove snaps - should trigger snaptrim
+ rados_ioctx_snap_set_read(ioctx, LIBRADOS_SNAP_HEAD);
+ for (unsigned snap = 0; snap < my_snaps.size(); ++snap) {
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps[snap]));
+ }
+
+ // sleep for few secs for the trim stats to populate
+ std::cout << "Waiting for snaptrim stats to be generated" << std::endl;
+ sleep(30);
+
+ // Dump pg stats and determine if snaptrim stats are getting set
+ int objects_trimmed = 0;
+ double snaptrim_duration = 0.0;
+ int tries = 0;
+ do {
+ char *buf, *st;
+ size_t buflen, stlen;
+ string c = string("{\"prefix\": \"pg dump\",\"format\":\"json\"}");
+ const char *cmd = c.c_str();
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)&cmd, 1, 0, 0,
+ &buf, &buflen, &st, &stlen));
+ string outstr(buf, buflen);
+ json_spirit::Value v;
+ ASSERT_NE(0, json_spirit::read(outstr, v)) << "Unable tp parse json."
+ << '\n' << outstr;
+
+ // pg dump object
+ json_spirit::Object& obj = v.get_obj();
+ get_snaptrim_stats(obj, &objects_trimmed, &snaptrim_duration);
+ if (objects_trimmed != num_objs) {
+ tries++;
+ objects_trimmed = 0;
+ std::cout << "Still waiting for all objects to be trimmed... " <<std::endl;
+ sleep(30);
+ }
+ } while (objects_trimmed != num_objs && tries < 5);
+
+ // final check for objects trimmed
+ ASSERT_EQ(objects_trimmed, num_objs);
+ std::cout << "Snaptrim duration: " << snaptrim_duration << std::endl;
+ ASSERT_GT(snaptrim_duration, 0.0);
+
+ // clean-up remaining objects
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_remove(ioctx, obj.c_str()));
+ }
+
+ delete[] buf;
+}
diff --git a/src/test/librados/snapshots_stats_cxx.cc b/src/test/librados/snapshots_stats_cxx.cc
new file mode 100644
index 000000000..f6be3a915
--- /dev/null
+++ b/src/test/librados/snapshots_stats_cxx.cc
@@ -0,0 +1,324 @@
+#include <algorithm>
+#include <errno.h>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "include/rados.h"
+#include "include/rados/librados.hpp"
+#include "json_spirit/json_spirit.h"
+#include "test/librados/test_cxx.h"
+#include "test/librados/testcase_cxx.h"
+
+using namespace librados;
+
+using std::string;
+
+class LibRadosSnapshotStatsSelfManagedPP : public RadosTestPP {
+public:
+ LibRadosSnapshotStatsSelfManagedPP() {};
+ ~LibRadosSnapshotStatsSelfManagedPP() override {};
+protected:
+ void SetUp() override {
+ // disable pg autoscaler for the tests
+ string cmd =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"off\""
+ "}";
+ std::cout << "Setting pg_autoscaler to 'off'" << std::endl;
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ // disable scrubs for the test
+ cmd = "{\"prefix\": \"osd set\",\"key\":\"noscrub\"}";
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+ cmd = "{\"prefix\": \"osd set\",\"key\":\"nodeep-scrub\"}";
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ RadosTestPP::SetUp();
+ }
+
+ void TearDown() override {
+ // re-enable pg autoscaler
+ string cmd =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"on\""
+ "}";
+ std::cout << "Setting pg_autoscaler to 'on'" << std::endl;
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ // re-enable scrubs
+ cmd = "{\"prefix\": \"osd unset\",\"key\":\"noscrub\"}";
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+ cmd = string("{\"prefix\": \"osd unset\",\"key\":\"nodeep-scrub\"}");
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ RadosTestPP::TearDown();
+ }
+};
+
+class LibRadosSnapshotStatsSelfManagedECPP : public RadosTestECPP {
+public:
+ LibRadosSnapshotStatsSelfManagedECPP() {};
+ ~LibRadosSnapshotStatsSelfManagedECPP() override {};
+protected:
+ void SetUp() override {
+ // disable pg autoscaler for the tests
+ string cmd =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"off\""
+ "}";
+ std::cout << "Setting pg_autoscaler to 'off'" << std::endl;
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ // disable scrubs for the test
+ cmd = string("{\"prefix\": \"osd set\",\"key\":\"noscrub\"}");
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+ cmd = string("{\"prefix\": \"osd set\",\"key\":\"nodeep-scrub\"}");
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ RadosTestECPP::SetUp();
+ }
+
+ void TearDown() override {
+ // re-enable pg autoscaler
+ string cmd =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"on\""
+ "}";
+ std::cout << "Setting pg_autoscaler to 'on'" << std::endl;
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ // re-enable scrubs
+ cmd = string("{\"prefix\": \"osd unset\",\"key\":\"noscrub\"}");
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+ cmd = string("{\"prefix\": \"osd unset\",\"key\":\"nodeep-scrub\"}");
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ RadosTestECPP::TearDown();
+ }
+};
+
+void get_snaptrim_stats(json_spirit::Object& pg_dump,
+ int *objs_trimmed,
+ double *trim_duration) {
+ // pg_map
+ json_spirit::Object pgmap;
+ for (json_spirit::Object::size_type i = 0; i < pg_dump.size(); ++i) {
+ json_spirit::Pair& p = pg_dump[i];
+ if (p.name_ == "pg_map") {
+ pgmap = p.value_.get_obj();
+ break;
+ }
+ }
+
+ // pg_stats array
+ json_spirit::Array pgs;
+ for (json_spirit::Object::size_type i = 0; i < pgmap.size(); ++i) {
+ json_spirit::Pair& p = pgmap[i];
+ if (p.name_ == "pg_stats") {
+ pgs = p.value_.get_array();
+ break;
+ }
+ }
+
+ // snaptrim stats
+ for (json_spirit::Object::size_type j = 0; j < pgs.size(); ++j) {
+ json_spirit::Object& pg_stat = pgs[j].get_obj();
+ for(json_spirit::Object::size_type k = 0; k < pg_stat.size(); ++k) {
+ json_spirit::Pair& stats = pg_stat[k];
+ if (stats.name_ == "objects_trimmed") {
+ *objs_trimmed += stats.value_.get_int();
+ }
+ if (stats.name_ == "snaptrim_duration") {
+ *trim_duration += stats.value_.get_real();
+ }
+ }
+ }
+}
+const int bufsize = 128;
+
+TEST_F(LibRadosSnapshotStatsSelfManagedPP, SnaptrimStatsPP) {
+ int num_objs = 10;
+
+ // create objects
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.write(obj, bl, sizeof(buf), 0));
+ }
+
+ std::vector<uint64_t> my_snaps;
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ for (int snap = 0; snap < 1; ++snap) {
+ // create a snapshot, clone
+ std::vector<uint64_t> ns(1);
+ ns.insert(ns.end(), my_snaps.begin(), my_snaps.end());
+ my_snaps.swap(ns);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.write(obj, bl2, sizeof(buf2), 0));
+ }
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // remove snaps - should trigger snaptrim
+ for (unsigned snap = 0; snap < my_snaps.size(); ++snap) {
+ ioctx.selfmanaged_snap_remove(my_snaps[snap]);
+ }
+
+ // sleep for few secs for the trim stats to populate
+ std::cout << "Waiting for snaptrim stats to be generated" << std::endl;
+ sleep(30);
+
+ // Dump pg stats and determine if snaptrim stats are getting set
+ int objects_trimmed = 0;
+ double snaptrim_duration = 0.0;
+ int tries = 0;
+ do {
+ string cmd = string("{\"prefix\": \"pg dump\",\"format\":\"json\"}");
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, cluster.mon_command(cmd, inbl, &outbl, NULL));
+ string outstr(outbl.c_str(), outbl.length());
+ json_spirit::Value v;
+ ASSERT_NE(0, json_spirit::read(outstr, v)) << "unable to parse json." << '\n' << outstr;
+
+ // pg_map
+ json_spirit::Object& obj = v.get_obj();
+ get_snaptrim_stats(obj, &objects_trimmed, &snaptrim_duration);
+ if (objects_trimmed < num_objs) {
+ tries++;
+ objects_trimmed = 0;
+ std::cout << "Still waiting for all objects to be trimmed... " <<std::endl;
+ sleep(30);
+ }
+ } while(objects_trimmed < num_objs && tries < 5);
+
+ // final check for objects trimmed
+ ASSERT_EQ(objects_trimmed, num_objs);
+ std::cout << "Snaptrim duration: " << snaptrim_duration << std::endl;
+ ASSERT_GT(snaptrim_duration, 0.0);
+
+ // clean-up remaining objects
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.remove(obj));
+ }
+}
+
+// EC testing
+TEST_F(LibRadosSnapshotStatsSelfManagedECPP, SnaptrimStatsECPP) {
+ int num_objs = 10;
+ int bsize = alignment;
+
+ // create objects
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ bufferlist bl;
+ bl.append(buf, bsize);
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.write(obj, bl, bsize, 0));
+ }
+
+ std::vector<uint64_t> my_snaps;
+ char *buf2 = (char *)new char[bsize];
+ memset(buf2, 0xdd, bsize);
+ bufferlist bl2;
+ bl2.append(buf2, bsize);
+ for (int snap = 0; snap < 1; ++snap) {
+ // create a snapshot, clone
+ std::vector<uint64_t> ns(1);
+ ns.insert(ns.end(), my_snaps.begin(), my_snaps.end());
+ my_snaps.swap(ns);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.write(obj, bl2, bsize, bsize));
+ }
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // remove snaps - should trigger snaptrim
+ for (unsigned snap = 0; snap < my_snaps.size(); ++snap) {
+ ioctx.selfmanaged_snap_remove(my_snaps[snap]);
+ }
+
+ // sleep for few secs for the trim stats to populate
+ std::cout << "Waiting for snaptrim stats to be generated" << std::endl;
+ sleep(30);
+
+ // Dump pg stats and determine if snaptrim stats are getting set
+ int objects_trimmed = 0;
+ double snaptrim_duration = 0.0;
+ int tries = 0;
+ do {
+ string cmd = string("{\"prefix\": \"pg dump\",\"format\":\"json\"}");
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, cluster.mon_command(cmd, inbl, &outbl, NULL));
+ string outstr(outbl.c_str(), outbl.length());
+ json_spirit::Value v;
+ ASSERT_NE(0, json_spirit::read(outstr, v)) << "unable to parse json." << '\n' << outstr;
+
+ // pg_map
+ json_spirit::Object& obj = v.get_obj();
+ get_snaptrim_stats(obj, &objects_trimmed, &snaptrim_duration);
+ if (objects_trimmed < num_objs) {
+ tries++;
+ objects_trimmed = 0;
+ std::cout << "Still waiting for all objects to be trimmed... " <<std::endl;
+ sleep(30);
+ }
+ } while(objects_trimmed < num_objs && tries < 5);
+
+ // final check for objects trimmed
+ ASSERT_EQ(objects_trimmed, num_objs);
+ std::cout << "Snaptrim duration: " << snaptrim_duration << std::endl;
+ ASSERT_GT(snaptrim_duration, 0.0);
+
+ // clean-up remaining objects
+ ioctx.snap_set_read(LIBRADOS_SNAP_HEAD);
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.remove(obj));
+ }
+
+ delete[] buf;
+ delete[] buf2;
+}
diff --git a/src/test/librados/stat.cc b/src/test/librados/stat.cc
new file mode 100644
index 000000000..6ae14395e
--- /dev/null
+++ b/src/test/librados/stat.cc
@@ -0,0 +1,153 @@
+#include "include/rados/librados.h"
+#include "test/librados/test.h"
+#include "test/librados/TestCase.h"
+
+#include "common/ceph_time.h"
+
+#include <algorithm>
+#include <errno.h>
+#include "gtest/gtest.h"
+#include "crimson_utils.h"
+
+typedef RadosTest LibRadosStat;
+typedef RadosTestEC LibRadosStatEC;
+
+TEST_F(LibRadosStat, Stat) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ uint64_t size = 0;
+ time_t mtime = 0;
+ ASSERT_EQ(0, rados_stat(ioctx, "foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(-ENOENT, rados_stat(ioctx, "nonexistent", &size, &mtime));
+}
+
+TEST_F(LibRadosStat, Stat2) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ rados_write_op_t op = rados_create_write_op();
+ rados_write_op_write(op, buf, sizeof(buf), 0);
+ struct timespec ts;
+ ts.tv_sec = 1457129052;
+ ts.tv_nsec = 123456789;
+ ASSERT_EQ(0, rados_write_op_operate2(op, ioctx, "foo", &ts, 0));
+ rados_release_write_op(op);
+
+ uint64_t size = 0;
+ time_t mtime = 0;
+ ASSERT_EQ(0, rados_stat(ioctx, "foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(mtime, ts.tv_sec);
+
+ struct timespec ts2 = {};
+ ASSERT_EQ(0, rados_stat2(ioctx, "foo", &size, &ts2));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(ts2.tv_sec, ts.tv_sec);
+ ASSERT_EQ(ts2.tv_nsec, ts.tv_nsec);
+
+ ASSERT_EQ(-ENOENT, rados_stat2(ioctx, "nonexistent", &size, &ts2));
+}
+
+TEST_F(LibRadosStat, StatNS) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ rados_ioctx_set_namespace(ioctx, "");
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_write(ioctx, "foo2", buf, sizeof(buf), 0));
+
+ char buf2[64];
+ memset(buf2, 0xcc, sizeof(buf2));
+ rados_ioctx_set_namespace(ioctx, "nspace");
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf2, sizeof(buf2), 0));
+
+ uint64_t size = 0;
+ time_t mtime = 0;
+ rados_ioctx_set_namespace(ioctx, "");
+ ASSERT_EQ(0, rados_stat(ioctx, "foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(-ENOENT, rados_stat(ioctx, "nonexistent", &size, &mtime));
+
+ rados_ioctx_set_namespace(ioctx, "nspace");
+ ASSERT_EQ(0, rados_stat(ioctx, "foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf2), size);
+ ASSERT_EQ(-ENOENT, rados_stat(ioctx, "nonexistent", &size, &mtime));
+ ASSERT_EQ(-ENOENT, rados_stat(ioctx, "foo2", &size, &mtime));
+}
+
+TEST_F(LibRadosStat, ClusterStat) {
+ struct rados_cluster_stat_t result;
+ ASSERT_EQ(0, rados_cluster_stat(cluster, &result));
+}
+
+TEST_F(LibRadosStat, PoolStat) {
+ char buf[128];
+ char actual_pool_name[80];
+ unsigned l = rados_ioctx_get_pool_name(ioctx, actual_pool_name, sizeof(actual_pool_name));
+ ASSERT_EQ(strlen(actual_pool_name), l);
+ ASSERT_EQ(0, strcmp(actual_pool_name, pool_name.c_str()));
+ memset(buf, 0xff, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ struct rados_pool_stat_t stats;
+ memset(&stats, 0, sizeof(stats));
+ ASSERT_EQ(0, rados_ioctx_pool_stat(ioctx, &stats));
+}
+
+TEST_F(LibRadosStatEC, Stat) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ uint64_t size = 0;
+ time_t mtime = 0;
+ ASSERT_EQ(0, rados_stat(ioctx, "foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(-ENOENT, rados_stat(ioctx, "nonexistent", &size, &mtime));
+}
+
+TEST_F(LibRadosStatEC, StatNS) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ rados_ioctx_set_namespace(ioctx, "");
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_write(ioctx, "foo2", buf, sizeof(buf), 0));
+
+ char buf2[64];
+ memset(buf2, 0xcc, sizeof(buf2));
+ rados_ioctx_set_namespace(ioctx, "nspace");
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf2, sizeof(buf2), 0));
+
+ uint64_t size = 0;
+ time_t mtime = 0;
+ rados_ioctx_set_namespace(ioctx, "");
+ ASSERT_EQ(0, rados_stat(ioctx, "foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(-ENOENT, rados_stat(ioctx, "nonexistent", &size, &mtime));
+
+ rados_ioctx_set_namespace(ioctx, "nspace");
+ ASSERT_EQ(0, rados_stat(ioctx, "foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf2), size);
+ ASSERT_EQ(-ENOENT, rados_stat(ioctx, "nonexistent", &size, &mtime));
+ ASSERT_EQ(-ENOENT, rados_stat(ioctx, "foo2", &size, &mtime));
+}
+
+TEST_F(LibRadosStatEC, ClusterStat) {
+ SKIP_IF_CRIMSON();
+ struct rados_cluster_stat_t result;
+ ASSERT_EQ(0, rados_cluster_stat(cluster, &result));
+}
+
+TEST_F(LibRadosStatEC, PoolStat) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ char actual_pool_name[80];
+ unsigned l = rados_ioctx_get_pool_name(ioctx, actual_pool_name, sizeof(actual_pool_name));
+ ASSERT_EQ(strlen(actual_pool_name), l);
+ ASSERT_EQ(0, strcmp(actual_pool_name, pool_name.c_str()));
+ memset(buf, 0xff, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ struct rados_pool_stat_t stats;
+ memset(&stats, 0, sizeof(stats));
+ ASSERT_EQ(0, rados_ioctx_pool_stat(ioctx, &stats));
+}
diff --git a/src/test/librados/stat_cxx.cc b/src/test/librados/stat_cxx.cc
new file mode 100644
index 000000000..07b480ad0
--- /dev/null
+++ b/src/test/librados/stat_cxx.cc
@@ -0,0 +1,168 @@
+#include "gtest/gtest.h"
+
+#include "include/rados/librados.hpp"
+
+#include "test/librados/test_cxx.h"
+#include "test/librados/testcase_cxx.h"
+#include "crimson_utils.h"
+
+using namespace librados;
+
+typedef RadosTestPP LibRadosStatPP;
+typedef RadosTestECPP LibRadosStatECPP;
+
+TEST_F(LibRadosStatPP, StatPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ uint64_t size;
+ time_t mtime;
+ ASSERT_EQ(0, ioctx.stat("foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(-ENOENT, ioctx.stat("nonexistent", &size, &mtime));
+}
+
+TEST_F(LibRadosStatPP, Stat2Mtime2PP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ librados::ObjectWriteOperation op;
+ struct timespec ts;
+ ts.tv_sec = 1457129052;
+ ts.tv_nsec = 123456789;
+ op.mtime2(&ts);
+ op.write(0, bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+
+ /* XXX time comparison asserts could spuriously fail */
+
+ uint64_t size;
+ time_t mtime;
+ ASSERT_EQ(0, ioctx.stat("foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(mtime, ts.tv_sec);
+
+ struct timespec ts2;
+ ASSERT_EQ(0, ioctx.stat2("foo", &size, &ts2));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(ts2.tv_sec, ts.tv_sec);
+ ASSERT_EQ(ts2.tv_nsec, ts.tv_nsec);
+
+ ASSERT_EQ(-ENOENT, ioctx.stat2("nonexistent", &size, &ts2));
+}
+
+TEST_F(LibRadosStatPP, ClusterStatPP) {
+ cluster_stat_t cstat;
+ ASSERT_EQ(0, cluster.cluster_stat(cstat));
+}
+
+TEST_F(LibRadosStatPP, PoolStatPP) {
+ std::string n = ioctx.get_pool_name();
+ ASSERT_EQ(n, pool_name);
+ char buf[128];
+ memset(buf, 0xff, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ std::list<std::string> v;
+ std::map<std::string,stats_map> stats;
+ ASSERT_EQ(0, cluster.get_pool_stats(v, stats));
+}
+
+TEST_F(LibRadosStatECPP, StatPP) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ uint64_t size;
+ time_t mtime;
+ ASSERT_EQ(0, ioctx.stat("foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(-ENOENT, ioctx.stat("nonexistent", &size, &mtime));
+}
+
+TEST_F(LibRadosStatECPP, ClusterStatPP) {
+ SKIP_IF_CRIMSON();
+ cluster_stat_t cstat;
+ ASSERT_EQ(0, cluster.cluster_stat(cstat));
+}
+
+TEST_F(LibRadosStatECPP, PoolStatPP) {
+ SKIP_IF_CRIMSON();
+ std::string n = ioctx.get_pool_name();
+ ASSERT_EQ(n, pool_name);
+ char buf[128];
+ memset(buf, 0xff, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ std::list<std::string> v;
+ std::map<std::string,stats_map> stats;
+ ASSERT_EQ(0, cluster.get_pool_stats(v, stats));
+}
+
+TEST_F(LibRadosStatPP, StatPPNS) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ioctx.set_namespace("");
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.write("foo2", bl, sizeof(buf), 0));
+
+ char buf2[64];
+ memset(buf2, 0xbb, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ioctx.set_namespace("nspace");
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), 0));
+
+ uint64_t size;
+ time_t mtime;
+ ioctx.set_namespace("");
+ ASSERT_EQ(0, ioctx.stat("foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(-ENOENT, ioctx.stat("nonexistent", &size, &mtime));
+
+ ioctx.set_namespace("nspace");
+ ASSERT_EQ(0, ioctx.stat("foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf2), size);
+ ASSERT_EQ(-ENOENT, ioctx.stat("nonexistent", &size, &mtime));
+ ASSERT_EQ(-ENOENT, ioctx.stat("foo2", &size, &mtime));
+}
+
+TEST_F(LibRadosStatECPP, StatPPNS) {
+ SKIP_IF_CRIMSON();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ioctx.set_namespace("");
+ ASSERT_EQ(0, ioctx.write("foo", bl, sizeof(buf), 0));
+ ASSERT_EQ(0, ioctx.write("foo2", bl, sizeof(buf), 0));
+
+ char buf2[64];
+ memset(buf2, 0xbb, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ioctx.set_namespace("nspace");
+ ASSERT_EQ(0, ioctx.write("foo", bl2, sizeof(buf2), 0));
+
+ uint64_t size;
+ time_t mtime;
+ ioctx.set_namespace("");
+ ASSERT_EQ(0, ioctx.stat("foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf), size);
+ ASSERT_EQ(-ENOENT, ioctx.stat("nonexistent", &size, &mtime));
+
+ ioctx.set_namespace("nspace");
+ ASSERT_EQ(0, ioctx.stat("foo", &size, &mtime));
+ ASSERT_EQ(sizeof(buf2), size);
+ ASSERT_EQ(-ENOENT, ioctx.stat("nonexistent", &size, &mtime));
+ ASSERT_EQ(-ENOENT, ioctx.stat("foo2", &size, &mtime));
+}
diff --git a/src/test/librados/test.cc b/src/test/librados/test.cc
new file mode 100644
index 000000000..be1b4faf8
--- /dev/null
+++ b/src/test/librados/test.cc
@@ -0,0 +1,198 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*
+// vim: ts=8 sw=2 smarttab
+
+#include "include/rados/librados.h"
+#include "include/rados/librados.hpp"
+#include "test/librados/test.h"
+
+#include "include/stringify.h"
+#include "common/ceph_context.h"
+#include "common/config.h"
+
+#include <errno.h>
+#include <sstream>
+#include <stdlib.h>
+#include <string>
+#include <time.h>
+#include <unistd.h>
+#include <iostream>
+#include "gtest/gtest.h"
+
+std::string create_one_pool(
+ const std::string &pool_name, rados_t *cluster, uint32_t pg_num)
+{
+ std::string err_str = connect_cluster(cluster);
+ if (err_str.length())
+ return err_str;
+
+ int ret = rados_pool_create(*cluster, pool_name.c_str());
+ if (ret) {
+ rados_shutdown(*cluster);
+ std::ostringstream oss;
+ oss << "create_one_pool(" << pool_name << ") failed with error " << ret;
+ return oss.str();
+ }
+
+ rados_ioctx_t ioctx;
+ ret = rados_ioctx_create(*cluster, pool_name.c_str(), &ioctx);
+ if (ret < 0) {
+ rados_shutdown(*cluster);
+ std::ostringstream oss;
+ oss << "rados_ioctx_create(" << pool_name << ") failed with error " << ret;
+ return oss.str();
+ }
+
+ rados_application_enable(ioctx, "rados", 1);
+ rados_ioctx_destroy(ioctx);
+ return "";
+}
+
+int destroy_ec_profile(rados_t *cluster,
+ const std::string& pool_name,
+ std::ostream &oss)
+{
+ char buf[1000];
+ snprintf(buf, sizeof(buf),
+ "{\"prefix\": \"osd erasure-code-profile rm\", \"name\": \"testprofile-%s\"}",
+ pool_name.c_str());
+ char *cmd[2];
+ cmd[0] = buf;
+ cmd[1] = NULL;
+ int ret = rados_mon_command(*cluster, (const char **)cmd, 1, "", 0, NULL,
+ 0, NULL, 0);
+ if (ret)
+ oss << "rados_mon_command: erasure-code-profile rm testprofile-"
+ << pool_name << " failed with error " << ret;
+ return ret;
+}
+
+int destroy_rule(rados_t *cluster,
+ const std::string &rule,
+ std::ostream &oss)
+{
+ char *cmd[2];
+ std::string tmp = ("{\"prefix\": \"osd crush rule rm\", \"name\":\"" +
+ rule + "\"}");
+ cmd[0] = (char*)tmp.c_str();
+ cmd[1] = NULL;
+ int ret = rados_mon_command(*cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0);
+ if (ret)
+ oss << "rados_mon_command: osd crush rule rm " + rule + " failed with error " << ret;
+ return ret;
+}
+
+int destroy_ec_profile_and_rule(rados_t *cluster,
+ const std::string &rule,
+ std::ostream &oss)
+{
+ int ret;
+ ret = destroy_ec_profile(cluster, rule, oss);
+ if (ret)
+ return ret;
+ return destroy_rule(cluster, rule, oss);
+}
+
+
+std::string create_one_ec_pool(const std::string &pool_name, rados_t *cluster)
+{
+ std::string err = connect_cluster(cluster);
+ if (err.length())
+ return err;
+
+ std::ostringstream oss;
+ int ret = destroy_ec_profile_and_rule(cluster, pool_name, oss);
+ if (ret) {
+ rados_shutdown(*cluster);
+ return oss.str();
+ }
+
+ char *cmd[2];
+ cmd[1] = NULL;
+
+ std::string profile_create = "{\"prefix\": \"osd erasure-code-profile set\", \"name\": \"testprofile-" + pool_name + "\", \"profile\": [ \"k=2\", \"m=1\", \"crush-failure-domain=osd\"]}";
+ cmd[0] = (char *)profile_create.c_str();
+ ret = rados_mon_command(*cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0);
+ if (ret) {
+ rados_shutdown(*cluster);
+ oss << "rados_mon_command erasure-code-profile set name:testprofile-" << pool_name << " failed with error " << ret;
+ return oss.str();
+ }
+
+ std::string cmdstr = "{\"prefix\": \"osd pool create\", \"pool\": \"" +
+ pool_name + "\", \"pool_type\":\"erasure\", \"pg_num\":8, \"pgp_num\":8, \"erasure_code_profile\":\"testprofile-" + pool_name + "\"}";
+ cmd[0] = (char *)cmdstr.c_str();
+ ret = rados_mon_command(*cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0);
+ if (ret) {
+ destroy_ec_profile(cluster, pool_name, oss);
+ rados_shutdown(*cluster);
+ oss << "rados_mon_command osd pool create failed with error " << ret;
+ return oss.str();
+ }
+
+ rados_wait_for_latest_osdmap(*cluster);
+ return "";
+}
+
+std::string connect_cluster(rados_t *cluster)
+{
+ char *id = getenv("CEPH_CLIENT_ID");
+ if (id) std::cerr << "Client id is: " << id << std::endl;
+
+ int ret;
+ ret = rados_create(cluster, NULL);
+ if (ret) {
+ std::ostringstream oss;
+ oss << "rados_create failed with error " << ret;
+ return oss.str();
+ }
+ ret = rados_conf_read_file(*cluster, NULL);
+ if (ret) {
+ rados_shutdown(*cluster);
+ std::ostringstream oss;
+ oss << "rados_conf_read_file failed with error " << ret;
+ return oss.str();
+ }
+ rados_conf_parse_env(*cluster, NULL);
+ ret = rados_connect(*cluster);
+ if (ret) {
+ rados_shutdown(*cluster);
+ std::ostringstream oss;
+ oss << "rados_connect failed with error " << ret;
+ return oss.str();
+ }
+ return "";
+}
+
+int destroy_one_pool(const std::string &pool_name, rados_t *cluster)
+{
+ int ret = rados_pool_delete(*cluster, pool_name.c_str());
+ if (ret) {
+ rados_shutdown(*cluster);
+ return ret;
+ }
+ rados_shutdown(*cluster);
+ return 0;
+}
+
+int destroy_one_ec_pool(const std::string &pool_name, rados_t *cluster)
+{
+ int ret = rados_pool_delete(*cluster, pool_name.c_str());
+ if (ret) {
+ rados_shutdown(*cluster);
+ return ret;
+ }
+
+ CephContext *cct = static_cast<CephContext*>(rados_cct(*cluster));
+ if (!cct->_conf->mon_fake_pool_delete) { // hope this is in [global]
+ std::ostringstream oss;
+ ret = destroy_ec_profile_and_rule(cluster, pool_name, oss);
+ if (ret) {
+ rados_shutdown(*cluster);
+ return ret;
+ }
+ }
+
+ rados_wait_for_latest_osdmap(*cluster);
+ rados_shutdown(*cluster);
+ return ret;
+}
diff --git a/src/test/librados/test.h b/src/test/librados/test.h
new file mode 100644
index 000000000..b3e0115fb
--- /dev/null
+++ b/src/test/librados/test.h
@@ -0,0 +1,32 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2011 New Dream Network
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef CEPH_TEST_RADOS_API_TEST_H
+#define CEPH_TEST_RADOS_API_TEST_H
+
+#include "include/rados/librados.h"
+#include "test/librados/test_shared.h"
+
+#include <map>
+#include <string>
+#include <unistd.h>
+
+std::string create_one_pool(const std::string &pool_name, rados_t *cluster,
+ uint32_t pg_num=0);
+std::string create_one_ec_pool(const std::string &pool_name, rados_t *cluster);
+std::string connect_cluster(rados_t *cluster);
+int destroy_one_pool(const std::string &pool_name, rados_t *cluster);
+int destroy_one_ec_pool(const std::string &pool_name, rados_t *cluster);
+
+#endif
diff --git a/src/test/librados/test_common.cc b/src/test/librados/test_common.cc
new file mode 100644
index 000000000..647a9ff48
--- /dev/null
+++ b/src/test/librados/test_common.cc
@@ -0,0 +1,167 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "common/Formatter.h"
+#include "include/stringify.h"
+#include "json_spirit/json_spirit.h"
+#include "test_common.h"
+
+using namespace std;
+
+namespace {
+
+using namespace ceph;
+
+int wait_for_healthy(rados_t *cluster)
+{
+ bool healthy = false;
+ // This timeout is very long because the tests are sometimes
+ // run on a thrashing cluster
+ int timeout = 3600;
+ int slept = 0;
+
+ while(!healthy) {
+ JSONFormatter cmd_f;
+ cmd_f.open_object_section("command");
+ cmd_f.dump_string("prefix", "status");
+ cmd_f.dump_string("format", "json");
+ cmd_f.close_section();
+ std::ostringstream cmd_stream;
+ cmd_f.flush(cmd_stream);
+ const std::string serialized_cmd = cmd_stream.str();
+
+ const char *cmd[2];
+ cmd[1] = NULL;
+ cmd[0] = serialized_cmd.c_str();
+
+ char *outbuf = NULL;
+ size_t outlen = 0;
+ int ret = rados_mon_command(*cluster, (const char **)cmd, 1, "", 0,
+ &outbuf, &outlen, NULL, NULL);
+ if (ret) {
+ return ret;
+ }
+
+ std::string out(outbuf, outlen);
+ rados_buffer_free(outbuf);
+
+ json_spirit::mValue root;
+ [[maybe_unused]] bool json_parse_success = json_spirit::read(out, root);
+ ceph_assert(json_parse_success);
+ json_spirit::mObject root_obj = root.get_obj();
+ json_spirit::mObject pgmap = root_obj["pgmap"].get_obj();
+ json_spirit::mArray pgs_by_state = pgmap["pgs_by_state"].get_array();
+
+ if (pgs_by_state.size() == 1) {
+ json_spirit::mObject state = pgs_by_state[0].get_obj();
+ std::string state_name = state["state_name"].get_str();
+ if (state_name != std::string("active+clean")) {
+ healthy = false;
+ } else {
+ healthy = true;
+ }
+ } else {
+ healthy = false;
+ }
+
+ if (slept >= timeout) {
+ return -ETIMEDOUT;
+ };
+
+ if (!healthy) {
+ sleep(1);
+ slept += 1;
+ }
+ }
+
+ return 0;
+}
+
+int rados_pool_set(
+ rados_t *cluster,
+ const std::string &pool_name,
+ const std::string &var,
+ const std::string &val)
+{
+ JSONFormatter cmd_f;
+ cmd_f.open_object_section("command");
+ cmd_f.dump_string("prefix", "osd pool set");
+ cmd_f.dump_string("pool", pool_name);
+ cmd_f.dump_string("var", var);
+ cmd_f.dump_string("val", val);
+ cmd_f.close_section();
+
+ std::ostringstream cmd_stream;
+ cmd_f.flush(cmd_stream);
+
+ const std::string serialized_cmd = cmd_stream.str();
+
+ const char *cmd[2];
+ cmd[1] = NULL;
+ cmd[0] = serialized_cmd.c_str();
+ int ret = rados_mon_command(*cluster, (const char **)cmd, 1, "", 0, NULL,
+ NULL, NULL, NULL);
+ return ret;
+}
+
+struct pool_op_error : std::exception {
+ string msg;
+ pool_op_error(const std::string& pool_name,
+ const std::string& func_name,
+ int err) {
+ std::ostringstream oss;
+ oss << func_name << "(" << pool_name << ") failed with error " << err;
+ msg = oss.str();
+ }
+ const char* what() const noexcept override {
+ return msg.c_str();
+ }
+};
+
+template<typename Func>
+std::string with_healthy_cluster(rados_t* cluster,
+ const std::string& pool_name,
+ Func&& func)
+{
+ try {
+ // Wait for 'creating/backfilling' to clear
+ if (int r = wait_for_healthy(cluster); r != 0) {
+ throw pool_op_error{pool_name, "wait_for_healthy", r};
+ }
+ func();
+ // Wait for 'creating/backfilling' to clear
+ if (int r = wait_for_healthy(cluster); r != 0) {
+ throw pool_op_error{pool_name, "wait_for_healthy", r};
+ }
+ } catch (const pool_op_error& e) {
+ return e.what();
+ }
+ return "";
+}
+}
+
+std::string set_pg_num(
+ rados_t *cluster, const std::string &pool_name, uint32_t pg_num)
+{
+ return with_healthy_cluster(cluster, pool_name, [&] {
+ // Adjust pg_num
+ if (int r = rados_pool_set(cluster, pool_name, "pg_num",
+ stringify(pg_num));
+ r != 0) {
+ throw pool_op_error{pool_name, "set_pg_num", r};
+ }
+ });
+}
+
+std::string set_pgp_num(
+ rados_t *cluster, const std::string &pool_name, uint32_t pgp_num)
+{
+ return with_healthy_cluster(cluster, pool_name, [&] {
+ // Adjust pgp_num
+ if (int r = rados_pool_set(cluster, pool_name, "pgp_num",
+ stringify(pgp_num));
+ r != 0) {
+ throw pool_op_error{pool_name, "set_pgp_num", r};
+ }
+ });
+}
diff --git a/src/test/librados/test_common.h b/src/test/librados/test_common.h
new file mode 100644
index 000000000..71ef9de2c
--- /dev/null
+++ b/src/test/librados/test_common.h
@@ -0,0 +1,9 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*
+// vim: ts=8 sw=2 smarttab
+
+#include "include/rados/librados.h"
+
+std::string set_pg_num(
+ rados_t *cluster, const std::string &pool_name, uint32_t pg_num);
+std::string set_pgp_num(
+ rados_t *cluster, const std::string &pool_name, uint32_t pgp_num);
diff --git a/src/test/librados/test_cxx.cc b/src/test/librados/test_cxx.cc
new file mode 100644
index 000000000..6c7e353e4
--- /dev/null
+++ b/src/test/librados/test_cxx.cc
@@ -0,0 +1,203 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*
+// vim: ts=8 sw=2 smarttab
+
+#include "test_cxx.h"
+
+#include "include/stringify.h"
+#include "common/ceph_context.h"
+#include "common/config.h"
+
+#include <errno.h>
+#include <sstream>
+#include <stdlib.h>
+#include <string>
+#include <time.h>
+#include <unistd.h>
+#include <iostream>
+#include "gtest/gtest.h"
+
+using namespace librados;
+
+std::string create_one_pool_pp(const std::string &pool_name, Rados &cluster)
+{
+ return create_one_pool_pp(pool_name, cluster, {});
+}
+std::string create_one_pool_pp(const std::string &pool_name, Rados &cluster,
+ const std::map<std::string, std::string> &config)
+{
+ std::string err = connect_cluster_pp(cluster, config);
+ if (err.length())
+ return err;
+ int ret = cluster.pool_create(pool_name.c_str());
+ if (ret) {
+ cluster.shutdown();
+ std::ostringstream oss;
+ oss << "cluster.pool_create(" << pool_name << ") failed with error " << ret;
+ return oss.str();
+ }
+
+ IoCtx ioctx;
+ ret = cluster.ioctx_create(pool_name.c_str(), ioctx);
+ if (ret < 0) {
+ cluster.shutdown();
+ std::ostringstream oss;
+ oss << "cluster.ioctx_create(" << pool_name << ") failed with error "
+ << ret;
+ return oss.str();
+ }
+ ioctx.application_enable("rados", true);
+ return "";
+}
+
+int destroy_rule_pp(Rados &cluster,
+ const std::string &rule,
+ std::ostream &oss)
+{
+ bufferlist inbl;
+ int ret = cluster.mon_command("{\"prefix\": \"osd crush rule rm\", \"name\":\"" +
+ rule + "\"}", inbl, NULL, NULL);
+ if (ret)
+ oss << "mon_command: osd crush rule rm " + rule + " failed with error " << ret << std::endl;
+ return ret;
+}
+
+int destroy_ec_profile_pp(Rados &cluster, const std::string& pool_name,
+ std::ostream &oss)
+{
+ bufferlist inbl;
+ int ret = cluster.mon_command("{\"prefix\": \"osd erasure-code-profile rm\", \"name\": \"testprofile-" + pool_name + "\"}",
+ inbl, NULL, NULL);
+ if (ret)
+ oss << "mon_command: osd erasure-code-profile rm testprofile-" << pool_name << " failed with error " << ret << std::endl;
+ return ret;
+}
+
+int destroy_ec_profile_and_rule_pp(Rados &cluster,
+ const std::string &rule,
+ std::ostream &oss)
+{
+ int ret;
+ ret = destroy_ec_profile_pp(cluster, rule, oss);
+ if (ret)
+ return ret;
+ return destroy_rule_pp(cluster, rule, oss);
+}
+
+std::string create_one_ec_pool_pp(const std::string &pool_name, Rados &cluster)
+{
+ std::string err = connect_cluster_pp(cluster);
+ if (err.length())
+ return err;
+
+ std::ostringstream oss;
+ int ret = destroy_ec_profile_and_rule_pp(cluster, pool_name, oss);
+ if (ret) {
+ cluster.shutdown();
+ return oss.str();
+ }
+
+ bufferlist inbl;
+ ret = cluster.mon_command(
+ "{\"prefix\": \"osd erasure-code-profile set\", \"name\": \"testprofile-" + pool_name + "\", \"profile\": [ \"k=2\", \"m=1\", \"crush-failure-domain=osd\"]}",
+ inbl, NULL, NULL);
+ if (ret) {
+ cluster.shutdown();
+ oss << "mon_command erasure-code-profile set name:testprofile-" << pool_name << " failed with error " << ret;
+ return oss.str();
+ }
+
+ ret = cluster.mon_command(
+ "{\"prefix\": \"osd pool create\", \"pool\": \"" + pool_name + "\", \"pool_type\":\"erasure\", \"pg_num\":8, \"pgp_num\":8, \"erasure_code_profile\":\"testprofile-" + pool_name + "\"}",
+ inbl, NULL, NULL);
+ if (ret) {
+ bufferlist inbl;
+ destroy_ec_profile_pp(cluster, pool_name, oss);
+ cluster.shutdown();
+ oss << "mon_command osd pool create pool:" << pool_name << " pool_type:erasure failed with error " << ret;
+ return oss.str();
+ }
+
+ cluster.wait_for_latest_osdmap();
+ return "";
+}
+
+std::string connect_cluster_pp(librados::Rados &cluster)
+{
+ return connect_cluster_pp(cluster, {});
+}
+
+std::string connect_cluster_pp(librados::Rados &cluster,
+ const std::map<std::string, std::string> &config)
+{
+ char *id = getenv("CEPH_CLIENT_ID");
+ if (id) std::cerr << "Client id is: " << id << std::endl;
+
+ int ret;
+ ret = cluster.init(id);
+ if (ret) {
+ std::ostringstream oss;
+ oss << "cluster.init failed with error " << ret;
+ return oss.str();
+ }
+ ret = cluster.conf_read_file(NULL);
+ if (ret) {
+ cluster.shutdown();
+ std::ostringstream oss;
+ oss << "cluster.conf_read_file failed with error " << ret;
+ return oss.str();
+ }
+ cluster.conf_parse_env(NULL);
+
+ for (auto &setting : config) {
+ ret = cluster.conf_set(setting.first.c_str(), setting.second.c_str());
+ if (ret) {
+ std::ostringstream oss;
+ oss << "failed to set config value " << setting.first << " to '"
+ << setting.second << "': " << strerror(-ret);
+ return oss.str();
+ }
+ }
+
+ ret = cluster.connect();
+ if (ret) {
+ cluster.shutdown();
+ std::ostringstream oss;
+ oss << "cluster.connect failed with error " << ret;
+ return oss.str();
+ }
+ return "";
+}
+
+int destroy_one_pool_pp(const std::string &pool_name, Rados &cluster)
+{
+ int ret = cluster.pool_delete(pool_name.c_str());
+ if (ret) {
+ cluster.shutdown();
+ return ret;
+ }
+ cluster.shutdown();
+ return 0;
+}
+
+int destroy_one_ec_pool_pp(const std::string &pool_name, Rados &cluster)
+{
+ int ret = cluster.pool_delete(pool_name.c_str());
+ if (ret) {
+ cluster.shutdown();
+ return ret;
+ }
+
+ CephContext *cct = static_cast<CephContext*>(cluster.cct());
+ if (!cct->_conf->mon_fake_pool_delete) { // hope this is in [global]
+ std::ostringstream oss;
+ ret = destroy_ec_profile_and_rule_pp(cluster, pool_name, oss);
+ if (ret) {
+ cluster.shutdown();
+ return ret;
+ }
+ }
+
+ cluster.wait_for_latest_osdmap();
+ cluster.shutdown();
+ return ret;
+}
diff --git a/src/test/librados/test_cxx.h b/src/test/librados/test_cxx.h
new file mode 100644
index 000000000..1d11d6923
--- /dev/null
+++ b/src/test/librados/test_cxx.h
@@ -0,0 +1,19 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#pragma once
+
+#include "include/rados/librados.hpp"
+#include "test/librados/test_shared.h"
+
+std::string create_one_pool_pp(const std::string &pool_name,
+ librados::Rados &cluster);
+std::string create_one_pool_pp(const std::string &pool_name,
+ librados::Rados &cluster,
+ const std::map<std::string, std::string> &config);
+std::string create_one_ec_pool_pp(const std::string &pool_name,
+ librados::Rados &cluster);
+std::string connect_cluster_pp(librados::Rados &cluster);
+std::string connect_cluster_pp(librados::Rados &cluster,
+ const std::map<std::string, std::string> &config);
+int destroy_one_pool_pp(const std::string &pool_name, librados::Rados &cluster);
+int destroy_one_ec_pool_pp(const std::string &pool_name, librados::Rados &cluster);
diff --git a/src/test/librados/test_shared.cc b/src/test/librados/test_shared.cc
new file mode 100644
index 000000000..8b50d112e
--- /dev/null
+++ b/src/test/librados/test_shared.cc
@@ -0,0 +1,44 @@
+#include "test_shared.h"
+
+#include <cstring>
+#include "gtest/gtest.h"
+#include "include/buffer.h"
+
+using namespace ceph;
+
+std::string get_temp_pool_name(const std::string &prefix)
+{
+ char hostname[80];
+ char out[160];
+ memset(hostname, 0, sizeof(hostname));
+ memset(out, 0, sizeof(out));
+ gethostname(hostname, sizeof(hostname)-1);
+ static int num = 1;
+ snprintf(out, sizeof(out), "%s-%d-%d", hostname, getpid(), num);
+ num++;
+ return prefix + out;
+}
+
+void assert_eq_sparse(bufferlist& expected,
+ const std::map<uint64_t, uint64_t>& extents,
+ bufferlist& actual) {
+ auto i = expected.begin();
+ auto p = actual.begin();
+ uint64_t pos = 0;
+ for (auto extent : extents) {
+ const uint64_t start = extent.first;
+ const uint64_t end = start + extent.second;
+ for (; pos < end; ++i, ++pos) {
+ ASSERT_FALSE(i.end());
+ if (pos < start) {
+ // check the hole
+ ASSERT_EQ('\0', *i);
+ } else {
+ // then the extent
+ ASSERT_EQ(*i, *p);
+ ++p;
+ }
+ }
+ }
+ ASSERT_EQ(expected.length(), pos);
+}
diff --git a/src/test/librados/test_shared.h b/src/test/librados/test_shared.h
new file mode 100644
index 000000000..3a18e916e
--- /dev/null
+++ b/src/test/librados/test_shared.h
@@ -0,0 +1,58 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*
+// vim: ts=8 sw=2 smarttab
+
+#pragma once
+
+#include <unistd.h>
+#include <chrono>
+#include <map>
+#include <string>
+#include <thread>
+
+#include "include/buffer_fwd.h"
+
+// helpers shared by librados tests
+std::string get_temp_pool_name(const std::string &prefix = "test-rados-api-");
+void assert_eq_sparse(ceph::bufferlist& expected,
+ const std::map<uint64_t, uint64_t>& extents,
+ ceph::bufferlist& actual);
+class TestAlarm
+{
+public:
+ #ifndef _WIN32
+ TestAlarm() {
+ alarm(2400);
+ }
+ ~TestAlarm() {
+ alarm(0);
+ }
+ #else
+ // TODO: add a timeout mechanism for Windows as well, possibly by using
+ // CreateTimerQueueTimer.
+ TestAlarm() {
+ }
+ ~TestAlarm() {
+ }
+ #endif
+};
+
+template<class Rep, class Period, typename Func, typename... Args,
+ typename Return = std::result_of_t<Func&&(Args&&...)>>
+Return wait_until(const std::chrono::duration<Rep, Period>& rel_time,
+ const std::chrono::duration<Rep, Period>& step,
+ const Return& expected,
+ Func&& func, Args&&... args)
+{
+ std::this_thread::sleep_for(rel_time - step);
+ for (auto& s : {step, step}) {
+ if (!s.count()) {
+ break;
+ }
+ auto ret = func(std::forward<Args>(args)...);
+ if (ret == expected) {
+ return ret;
+ }
+ std::this_thread::sleep_for(s);
+ }
+ return func(std::forward<Args>(args)...);
+}
diff --git a/src/test/librados/testcase_cxx.cc b/src/test/librados/testcase_cxx.cc
new file mode 100644
index 000000000..407c59b55
--- /dev/null
+++ b/src/test/librados/testcase_cxx.cc
@@ -0,0 +1,407 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "testcase_cxx.h"
+
+#include <errno.h>
+#include <fmt/format.h>
+#include "test_cxx.h"
+#include "test_shared.h"
+#include "crimson_utils.h"
+#include "include/scope_guard.h"
+
+using namespace librados;
+
+namespace {
+
+void init_rand() {
+ static bool seeded = false;
+ if (!seeded) {
+ seeded = true;
+ int seed = getpid();
+ std::cout << "seed " << seed << std::endl;
+ srand(seed);
+ }
+}
+
+} // anonymous namespace
+
+std::string RadosTestPPNS::pool_name;
+Rados RadosTestPPNS::s_cluster;
+
+void RadosTestPPNS::SetUpTestCase()
+{
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestPPNS::TearDownTestCase()
+{
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestPPNS::SetUp()
+{
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx));
+ bool req;
+ ASSERT_EQ(0, ioctx.pool_requires_alignment2(&req));
+ ASSERT_FALSE(req);
+}
+
+void RadosTestPPNS::TearDown()
+{
+ if (cleanup)
+ cleanup_all_objects(ioctx);
+ ioctx.close();
+}
+
+void RadosTestPPNS::cleanup_all_objects(librados::IoCtx ioctx)
+{
+ // remove all objects to avoid polluting other tests
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ ioctx.set_namespace(all_nspaces);
+ for (NObjectIterator it = ioctx.nobjects_begin();
+ it != ioctx.nobjects_end(); ++it) {
+ ioctx.locator_set_key(it->get_locator());
+ ioctx.set_namespace(it->get_nspace());
+ ASSERT_EQ(0, ioctx.remove(it->get_oid()));
+ }
+}
+
+std::string RadosTestParamPPNS::pool_name;
+std::string RadosTestParamPPNS::cache_pool_name;
+Rados RadosTestParamPPNS::s_cluster;
+
+void RadosTestParamPPNS::SetUpTestCase()
+{
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestParamPPNS::TearDownTestCase()
+{
+ if (cache_pool_name.length()) {
+ // tear down tiers
+ bufferlist inbl;
+ ASSERT_EQ(0, s_cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, s_cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, s_cluster.mon_command(
+ "{\"prefix\": \"osd pool delete\", \"pool\": \"" + cache_pool_name +
+ "\", \"pool2\": \"" + cache_pool_name + "\", \"yes_i_really_really_mean_it\": true}",
+ inbl, NULL, NULL));
+ cache_pool_name = "";
+ }
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestParamPPNS::SetUp()
+{
+ if (!is_crimson_cluster() && strcmp(GetParam(), "cache") == 0 &&
+ cache_pool_name.empty()) {
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ cache_pool_name = get_temp_pool_name();
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd pool create\", \"pool\": \"" + cache_pool_name +
+ "\", \"pg_num\": 4}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+ cluster.wait_for_latest_osdmap();
+ }
+
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx));
+ bool req;
+ ASSERT_EQ(0, ioctx.pool_requires_alignment2(&req));
+ ASSERT_FALSE(req);
+}
+
+void RadosTestParamPPNS::TearDown()
+{
+ if (cleanup)
+ cleanup_all_objects(ioctx);
+ ioctx.close();
+}
+
+void RadosTestParamPPNS::cleanup_all_objects(librados::IoCtx ioctx)
+{
+ // remove all objects to avoid polluting other tests
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ ioctx.set_namespace(all_nspaces);
+ for (NObjectIterator it = ioctx.nobjects_begin();
+ it != ioctx.nobjects_end(); ++it) {
+ ioctx.locator_set_key(it->get_locator());
+ ioctx.set_namespace(it->get_nspace());
+ ASSERT_EQ(0, ioctx.remove(it->get_oid()));
+ }
+}
+
+std::string RadosTestECPPNS::pool_name;
+Rados RadosTestECPPNS::s_cluster;
+
+void RadosTestECPPNS::SetUpTestCase()
+{
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_ec_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestECPPNS::TearDownTestCase()
+{
+ ASSERT_EQ(0, destroy_one_ec_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestECPPNS::SetUp()
+{
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx));
+ bool req;
+ ASSERT_EQ(0, ioctx.pool_requires_alignment2(&req));
+ ASSERT_TRUE(req);
+ ASSERT_EQ(0, ioctx.pool_required_alignment2(&alignment));
+ ASSERT_NE(0U, alignment);
+}
+
+void RadosTestECPPNS::TearDown()
+{
+ if (cleanup)
+ cleanup_all_objects(ioctx);
+ ioctx.close();
+}
+
+std::string RadosTestPP::pool_name;
+Rados RadosTestPP::s_cluster;
+
+void RadosTestPP::SetUpTestCase()
+{
+ init_rand();
+
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestPP::TearDownTestCase()
+{
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestPP::SetUp()
+{
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx));
+ nspace = get_temp_pool_name();
+ ioctx.set_namespace(nspace);
+ bool req;
+ ASSERT_EQ(0, ioctx.pool_requires_alignment2(&req));
+ ASSERT_FALSE(req);
+}
+
+void RadosTestPP::TearDown()
+{
+ if (cleanup) {
+ cleanup_default_namespace(ioctx);
+ cleanup_namespace(ioctx, nspace);
+ }
+ ioctx.close();
+}
+
+void RadosTestPP::cleanup_default_namespace(librados::IoCtx ioctx)
+{
+ // remove all objects from the default namespace to avoid polluting
+ // other tests
+ cleanup_namespace(ioctx, "");
+}
+
+void RadosTestPP::cleanup_namespace(librados::IoCtx ioctx, std::string ns)
+{
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ ioctx.set_namespace(ns);
+ int tries = 20;
+ while (--tries) {
+ int got_enoent = 0;
+ for (NObjectIterator it = ioctx.nobjects_begin();
+ it != ioctx.nobjects_end(); ++it) {
+ ioctx.locator_set_key(it->get_locator());
+ ObjectWriteOperation op;
+ op.remove();
+ librados::AioCompletion *completion = s_cluster.aio_create_completion();
+ auto sg = make_scope_guard([&] { completion->release(); });
+ ASSERT_EQ(0, ioctx.aio_operate(it->get_oid(), completion, &op,
+ librados::OPERATION_IGNORE_CACHE));
+ completion->wait_for_complete();
+ if (completion->get_return_value() == -ENOENT) {
+ ++got_enoent;
+ std::cout << " got ENOENT removing " << it->get_oid()
+ << " in ns " << ns << std::endl;
+ } else {
+ ASSERT_EQ(0, completion->get_return_value());
+ }
+ }
+ if (!got_enoent) {
+ break;
+ }
+ std::cout << " got ENOENT on " << got_enoent
+ << " objects, waiting a bit for snap"
+ << " trimming before retrying " << tries << " more times..."
+ << std::endl;
+ sleep(1);
+ }
+ if (tries == 0) {
+ std::cout << "failed to clean up; probably need to scrub purged_snaps."
+ << std::endl;
+ }
+}
+
+std::string RadosTestParamPP::pool_name;
+std::string RadosTestParamPP::cache_pool_name;
+Rados RadosTestParamPP::s_cluster;
+
+void RadosTestParamPP::SetUpTestCase()
+{
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestParamPP::TearDownTestCase()
+{
+ if (cache_pool_name.length()) {
+ // tear down tiers
+ bufferlist inbl;
+ ASSERT_EQ(0, s_cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, s_cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, s_cluster.mon_command(
+ "{\"prefix\": \"osd pool delete\", \"pool\": \"" + cache_pool_name +
+ "\", \"pool2\": \"" + cache_pool_name + "\", \"yes_i_really_really_mean_it\": true}",
+ inbl, NULL, NULL));
+ cache_pool_name = "";
+ }
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestParamPP::SetUp()
+{
+ if (!is_crimson_cluster() && strcmp(GetParam(), "cache") == 0 &&
+ cache_pool_name.empty()) {
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ cache_pool_name = get_temp_pool_name();
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd pool create\", \"pool\": \"" + cache_pool_name +
+ "\", \"pg_num\": 4}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+ cluster.wait_for_latest_osdmap();
+ }
+
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx));
+ nspace = get_temp_pool_name();
+ ioctx.set_namespace(nspace);
+ bool req;
+ ASSERT_EQ(0, ioctx.pool_requires_alignment2(&req));
+ ASSERT_FALSE(req);
+}
+
+void RadosTestParamPP::TearDown()
+{
+ if (cleanup) {
+ cleanup_default_namespace(ioctx);
+ cleanup_namespace(ioctx, nspace);
+ }
+ ioctx.close();
+}
+
+void RadosTestParamPP::cleanup_default_namespace(librados::IoCtx ioctx)
+{
+ // remove all objects from the default namespace to avoid polluting
+ // other tests
+ cleanup_namespace(ioctx, "");
+}
+
+void RadosTestParamPP::cleanup_namespace(librados::IoCtx ioctx, std::string ns)
+{
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ ioctx.set_namespace(ns);
+ for (NObjectIterator it = ioctx.nobjects_begin();
+ it != ioctx.nobjects_end(); ++it) {
+ ioctx.locator_set_key(it->get_locator());
+ ASSERT_EQ(0, ioctx.remove(it->get_oid()));
+ }
+}
+
+std::string RadosTestECPP::pool_name;
+Rados RadosTestECPP::s_cluster;
+
+void RadosTestECPP::SetUpTestCase()
+{
+ SKIP_IF_CRIMSON();
+ auto pool_prefix = fmt::format("{}_", ::testing::UnitTest::GetInstance()->current_test_case()->name());
+ pool_name = get_temp_pool_name(pool_prefix);
+ ASSERT_EQ("", create_one_ec_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestECPP::TearDownTestCase()
+{
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, destroy_one_ec_pool_pp(pool_name, s_cluster));
+}
+
+void RadosTestECPP::SetUp()
+{
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx));
+ nspace = get_temp_pool_name();
+ ioctx.set_namespace(nspace);
+ bool req;
+ ASSERT_EQ(0, ioctx.pool_requires_alignment2(&req));
+ ASSERT_TRUE(req);
+ ASSERT_EQ(0, ioctx.pool_required_alignment2(&alignment));
+ ASSERT_NE(0U, alignment);
+}
+
+void RadosTestECPP::TearDown()
+{
+ SKIP_IF_CRIMSON();
+ if (cleanup) {
+ cleanup_default_namespace(ioctx);
+ cleanup_namespace(ioctx, nspace);
+ }
+ ioctx.close();
+}
+
diff --git a/src/test/librados/testcase_cxx.h b/src/test/librados/testcase_cxx.h
new file mode 100644
index 000000000..637ec11ee
--- /dev/null
+++ b/src/test/librados/testcase_cxx.h
@@ -0,0 +1,130 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#pragma once
+
+#include "gtest/gtest.h"
+#include "include/rados/librados.hpp"
+
+class RadosTestPPNS : public ::testing::Test {
+public:
+ RadosTestPPNS(bool c=false) : cluster(s_cluster), cleanup(c) {}
+ ~RadosTestPPNS() override {}
+protected:
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+ static void cleanup_all_objects(librados::IoCtx ioctx);
+ static librados::Rados s_cluster;
+ static std::string pool_name;
+
+ void SetUp() override;
+ void TearDown() override;
+ librados::Rados &cluster;
+ librados::IoCtx ioctx;
+ bool cleanup;
+};
+
+struct RadosTestPPNSCleanup : public RadosTestPPNS {
+ RadosTestPPNSCleanup() : RadosTestPPNS(true) {}
+};
+
+class RadosTestParamPPNS : public ::testing::TestWithParam<const char*> {
+public:
+ RadosTestParamPPNS(bool c=false) : cluster(s_cluster), cleanup(c) {}
+ ~RadosTestParamPPNS() override {}
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+protected:
+ static void cleanup_all_objects(librados::IoCtx ioctx);
+ static librados::Rados s_cluster;
+ static std::string pool_name;
+ static std::string cache_pool_name;
+
+ void SetUp() override;
+ void TearDown() override;
+ librados::Rados &cluster;
+ librados::IoCtx ioctx;
+ bool cleanup;
+};
+
+class RadosTestECPPNS : public RadosTestPPNS {
+public:
+ RadosTestECPPNS(bool c=false) : cluster(s_cluster), cleanup(c) {}
+ ~RadosTestECPPNS() override {}
+protected:
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+ static librados::Rados s_cluster;
+ static std::string pool_name;
+
+ void SetUp() override;
+ void TearDown() override;
+ librados::Rados &cluster;
+ librados::IoCtx ioctx;
+ uint64_t alignment = 0;
+ bool cleanup;
+};
+
+struct RadosTestECPPNSCleanup : public RadosTestECPPNS {
+ RadosTestECPPNSCleanup() : RadosTestECPPNS(true) {}
+};
+
+class RadosTestPP : public ::testing::Test {
+public:
+ RadosTestPP(bool c=false) : cluster(s_cluster), cleanup(c) {}
+ ~RadosTestPP() override {}
+protected:
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+ static void cleanup_default_namespace(librados::IoCtx ioctx);
+ static void cleanup_namespace(librados::IoCtx ioctx, std::string ns);
+ static librados::Rados s_cluster;
+ static std::string pool_name;
+
+ void SetUp() override;
+ void TearDown() override;
+ librados::Rados &cluster;
+ librados::IoCtx ioctx;
+ bool cleanup;
+ std::string nspace;
+};
+
+class RadosTestParamPP : public ::testing::TestWithParam<const char*> {
+public:
+ RadosTestParamPP(bool c=false) : cluster(s_cluster), cleanup(c) {}
+ ~RadosTestParamPP() override {}
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+protected:
+ static void cleanup_default_namespace(librados::IoCtx ioctx);
+ static void cleanup_namespace(librados::IoCtx ioctx, std::string ns);
+ static librados::Rados s_cluster;
+ static std::string pool_name;
+ static std::string cache_pool_name;
+
+ void SetUp() override;
+ void TearDown() override;
+ librados::Rados &cluster;
+ librados::IoCtx ioctx;
+ bool cleanup;
+ std::string nspace;
+};
+
+class RadosTestECPP : public RadosTestPP {
+public:
+ RadosTestECPP(bool c=false) : cluster(s_cluster), cleanup(c) {}
+ ~RadosTestECPP() override {}
+protected:
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+ static librados::Rados s_cluster;
+ static std::string pool_name;
+
+ void SetUp() override;
+ void TearDown() override;
+ librados::Rados &cluster;
+ librados::IoCtx ioctx;
+ bool cleanup;
+ std::string nspace;
+ uint64_t alignment = 0;
+};
diff --git a/src/test/librados/tier_cxx.cc b/src/test/librados/tier_cxx.cc
new file mode 100644
index 000000000..be1d411a9
--- /dev/null
+++ b/src/test/librados/tier_cxx.cc
@@ -0,0 +1,9304 @@
+// -*- 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/buffer.h"
+#include "include/rbd_types.h"
+#include "include/rados/librados.hpp"
+#include "include/stringify.h"
+#include "include/types.h"
+#include "global/global_context.h"
+#include "common/Cond.h"
+#include "common/ceph_crypto.h"
+#include "test/librados/test_cxx.h"
+#include "test/librados/testcase_cxx.h"
+#include "json_spirit/json_spirit.h"
+#include "cls/cas/cls_cas_ops.h"
+#include "cls/cas/cls_cas_internal.h"
+
+#include "osd/HitSet.h"
+
+#include <errno.h>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "cls/cas/cls_cas_client.h"
+#include "cls/cas/cls_cas_internal.h"
+#include "crimson_utils.h"
+
+using namespace std;
+using namespace librados;
+using ceph::crypto::SHA1;
+
+typedef RadosTestPP LibRadosTierPP;
+typedef RadosTestECPP LibRadosTierECPP;
+
+void flush_evict_all(librados::Rados& cluster, librados::IoCtx& cache_ioctx)
+{
+ bufferlist inbl;
+ cache_ioctx.set_namespace(all_nspaces);
+ for (NObjectIterator it = cache_ioctx.nobjects_begin();
+ it != cache_ioctx.nobjects_end(); ++it) {
+ cache_ioctx.locator_set_key(it->get_locator());
+ cache_ioctx.set_namespace(it->get_nspace());
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ cache_ioctx.aio_operate(
+ it->get_oid(), completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL);
+ completion->wait_for_complete();
+ completion->get_return_value();
+ completion->release();
+ }
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ cache_ioctx.aio_operate(
+ it->get_oid(), completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL);
+ completion->wait_for_complete();
+ completion->get_return_value();
+ completion->release();
+ }
+ }
+}
+
+static string _get_required_osd_release(Rados& cluster)
+{
+ bufferlist inbl;
+ string cmd = string("{\"prefix\": \"osd dump\",\"format\":\"json\"}");
+ bufferlist outbl;
+ int r = cluster.mon_command(cmd, inbl, &outbl, NULL);
+ ceph_assert(r >= 0);
+ string outstr(outbl.c_str(), outbl.length());
+ json_spirit::Value v;
+ if (!json_spirit::read(outstr, v)) {
+ cerr <<" unable to parse json " << outstr << std::endl;
+ return "";
+ }
+
+ json_spirit::Object& o = v.get_obj();
+ for (json_spirit::Object::size_type i=0; i<o.size(); i++) {
+ json_spirit::Pair& p = o[i];
+ if (p.name_ == "require_osd_release") {
+ cout << "require_osd_release = " << p.value_.get_str() << std::endl;
+ return p.value_.get_str();
+ }
+ }
+ cerr << "didn't find require_osd_release in " << outstr << std::endl;
+ return "";
+}
+
+void manifest_set_chunk(Rados& cluster, librados::IoCtx& src_ioctx,
+ librados::IoCtx& tgt_ioctx,
+ uint64_t src_offset, uint64_t length,
+ std::string src_oid, std::string tgt_oid)
+{
+ ObjectReadOperation op;
+ op.set_chunk(src_offset, length, src_ioctx, src_oid, 0,
+ CEPH_OSD_OP_FLAG_WITH_REFERENCE);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, tgt_ioctx.aio_operate(tgt_oid, completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+}
+
+static inline void buf_to_hex(const unsigned char *buf, int len, char *str)
+{
+ int i;
+ str[0] = '\0';
+ for (i = 0; i < len; i++) {
+ sprintf(&str[i*2], "%02x", (int)buf[i]);
+ }
+}
+
+void check_fp_oid_refcount(librados::IoCtx& ioctx, std::string foid, uint64_t count,
+ std::string fp_algo = NULL)
+{
+ bufferlist t;
+ int size = foid.length();
+ if (fp_algo == "sha1") {
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ SHA1 sha1_gen;
+ sha1_gen.Update((const unsigned char *)foid.c_str(), size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ ioctx.getxattr(p_str, CHUNK_REFCOUNT_ATTR, t);
+ } else if (fp_algo.empty()) {
+ ioctx.getxattr(foid, CHUNK_REFCOUNT_ATTR, t);
+ } else if (!fp_algo.empty()) {
+ ceph_assert(0 == "unrecognized fingerprint algorithm");
+ }
+
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_LE(count, refs.count());
+}
+
+string get_fp_oid(string oid, std::string fp_algo = NULL)
+{
+ if (fp_algo == "sha1") {
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ SHA1 sha1_gen;
+ int size = oid.length();
+ sha1_gen.Update((const unsigned char *)oid.c_str(), size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ return string(p_str);
+ }
+
+ return string();
+}
+
+void is_intended_refcount_state(librados::IoCtx& src_ioctx,
+ std::string src_oid,
+ librados::IoCtx& dst_ioctx,
+ std::string dst_oid,
+ int expected_refcount)
+{
+ int src_refcount = 0, dst_refcount = 0;
+ bufferlist t;
+ int r = dst_ioctx.getxattr(dst_oid, CHUNK_REFCOUNT_ATTR, t);
+ if (r == -ENOENT) {
+ dst_refcount = 0;
+ } else {
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ceph_assert(0);
+ }
+ dst_refcount = refs.count();
+ }
+ int tries = 0;
+ for (; tries < 30; ++tries) {
+ r = cls_cas_references_chunk(src_ioctx, src_oid, dst_oid);
+ if (r == -ENOENT || r == -ENOLINK) {
+ src_refcount = 0;
+ } else if (r == -EBUSY) {
+ sleep(20);
+ continue;
+ } else {
+ src_refcount = r;
+ }
+ break;
+ }
+ ASSERT_TRUE(tries < 30);
+ ASSERT_TRUE(src_refcount >= 0);
+ ASSERT_TRUE(src_refcount == expected_refcount);
+ ASSERT_TRUE(src_refcount <= dst_refcount);
+}
+
+class LibRadosTwoPoolsPP : public RadosTestPP
+{
+public:
+ LibRadosTwoPoolsPP() {};
+ ~LibRadosTwoPoolsPP() override {};
+protected:
+ static void SetUpTestCase() {
+ pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool_pp(pool_name, s_cluster));
+ }
+ static void TearDownTestCase() {
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, s_cluster));
+ }
+ static std::string cache_pool_name;
+
+ void SetUp() override {
+ SKIP_IF_CRIMSON();
+ cache_pool_name = get_temp_pool_name();
+ ASSERT_EQ(0, s_cluster.pool_create(cache_pool_name.c_str()));
+ RadosTestPP::SetUp();
+
+ ASSERT_EQ(0, cluster.ioctx_create(cache_pool_name.c_str(), cache_ioctx));
+ cache_ioctx.application_enable("rados", true);
+ cache_ioctx.set_namespace(nspace);
+ }
+ void TearDown() override {
+ SKIP_IF_CRIMSON();
+ // flush + evict cache
+ flush_evict_all(cluster, cache_ioctx);
+
+ bufferlist inbl;
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+
+ RadosTestPP::TearDown();
+
+ cleanup_default_namespace(cache_ioctx);
+ cleanup_namespace(cache_ioctx, nspace);
+
+ cache_ioctx.close();
+ ASSERT_EQ(0, s_cluster.pool_delete(cache_pool_name.c_str()));
+ }
+ librados::IoCtx cache_ioctx;
+};
+
+class Completions
+{
+public:
+ Completions() = default;
+ librados::AioCompletion* getCompletion() {
+ librados::AioCompletion* comp = librados::Rados::aio_create_completion();
+ m_completions.push_back(comp);
+ return comp;
+ }
+
+ ~Completions() {
+ for (auto& comp : m_completions) {
+ comp->release();
+ }
+ }
+
+private:
+ vector<librados::AioCompletion *> m_completions;
+};
+
+Completions completions;
+
+std::string LibRadosTwoPoolsPP::cache_pool_name;
+
+TEST_F(LibRadosTierPP, Dirty) {
+ SKIP_IF_CRIMSON();
+ {
+ ObjectWriteOperation op;
+ op.undirty();
+ ASSERT_EQ(0, ioctx.operate("foo", &op)); // still get 0 if it dne
+ }
+ {
+ ObjectWriteOperation op;
+ op.create(true);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_TRUE(dirty);
+ ASSERT_EQ(0, r);
+ }
+ {
+ ObjectWriteOperation op;
+ op.undirty();
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ ObjectWriteOperation op;
+ op.undirty();
+ ASSERT_EQ(0, ioctx.operate("foo", &op)); // still 0 if already clean
+ }
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_FALSE(dirty);
+ ASSERT_EQ(0, r);
+ }
+ {
+ ObjectWriteOperation op;
+ op.truncate(0); // still a write even tho it is a no-op
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_TRUE(dirty);
+ ASSERT_EQ(0, r);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, Overlay) {
+ SKIP_IF_CRIMSON();
+ // create objects
+ {
+ bufferlist bl;
+ bl.append("base");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("cache");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // by default, the overlay sends us to cache pool
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, cache_ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ // unless we say otherwise
+ {
+ bufferlist bl;
+ ObjectReadOperation op;
+ op.read(0, 1, &bl, NULL);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ ASSERT_EQ('b', bl[0]);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, Promote) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger a promote
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ }
+
+ // read, trigger a whiteout
+ {
+ bufferlist bl;
+ ASSERT_EQ(-ENOENT, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ(-ENOENT, ioctx.read("bar", bl, 1, 0));
+ }
+
+ // verify the object is present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, PromoteSnap) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ ObjectWriteOperation op;
+ op.remove();
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger a promote on the head
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bam", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ ioctx.snap_set_read(my_snaps[0]);
+
+ // read foo snap
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ // read bar snap
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ // read baz snap
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("baz", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+
+ // read foo
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ // read bar
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ // read baz
+ {
+ bufferlist bl;
+ ASSERT_EQ(-ENOENT, ioctx.read("baz", bl, 1, 0));
+ }
+
+ // cleanup
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+}
+
+TEST_F(LibRadosTwoPoolsPP, PromoteSnapScrub) {
+ SKIP_IF_CRIMSON();
+ int num = 100;
+
+ // create objects
+ for (int i=0; i<num; ++i) {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate(string("foo") + stringify(i), &op));
+ }
+
+ vector<uint64_t> my_snaps;
+ for (int snap=0; snap<4; ++snap) {
+ // create a snapshot, clone
+ vector<uint64_t> ns(1);
+ ns.insert(ns.end(), my_snaps.begin(), my_snaps.end());
+ my_snaps.swap(ns);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ cout << "my_snaps " << my_snaps << std::endl;
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ for (int i=0; i<num; ++i) {
+ bufferlist bl;
+ bl.append(string("ciao! snap") + stringify(snap));
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate(string("foo") + stringify(i), &op));
+ }
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger a promote on _some_ heads to make sure we handle cases
+ // where snaps are present and where they are not.
+ cout << "promoting some heads" << std::endl;
+ for (int i=0; i<num; ++i) {
+ if (i % 5 == 0 || i > num - 3) {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read(string("foo") + stringify(i), bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ }
+
+ for (unsigned snap = 0; snap < my_snaps.size(); ++snap) {
+ cout << "promoting from clones for snap " << my_snaps[snap] << std::endl;
+ ioctx.snap_set_read(my_snaps[snap]);
+
+ // read some snaps, semi-randomly
+ for (int i=0; i<50; ++i) {
+ bufferlist bl;
+ string o = string("foo") + stringify((snap * i * 137) % 80);
+ //cout << o << std::endl;
+ ASSERT_EQ(1, ioctx.read(o, bl, 1, 0));
+ }
+ }
+
+ // ok, stop and scrub this pool (to make sure scrub can handle
+ // missing clones in the cache tier).
+ {
+ IoCtx cache_ioctx;
+ ASSERT_EQ(0, cluster.ioctx_create(cache_pool_name.c_str(), cache_ioctx));
+ for (int i=0; i<10; ++i) {
+ do {
+ ostringstream ss;
+ ss << "{\"prefix\": \"pg scrub\", \"pgid\": \""
+ << cache_ioctx.get_id() << "." << i
+ << "\"}";
+ int r = cluster.mon_command(ss.str(), inbl, NULL, NULL);
+ if (r == -ENOENT || // in case mgr osdmap is stale
+ r == -EAGAIN) {
+ sleep(5);
+ continue;
+ }
+ } while (false);
+ }
+
+ // give it a few seconds to go. this is sloppy but is usually enough time
+ cout << "waiting for scrubs..." << std::endl;
+ sleep(30);
+ cout << "done waiting" << std::endl;
+ }
+
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+
+ //cleanup
+ for (unsigned snap = 0; snap < my_snaps.size(); ++snap) {
+ ioctx.selfmanaged_snap_remove(my_snaps[snap]);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, PromoteSnapTrimRace) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // delete the snap
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps[0]));
+
+ ioctx.snap_set_read(my_snaps[0]);
+
+ // read foo snap. the OSD may or may not realize that this snap has
+ // been logically deleted; either response is valid.
+ {
+ bufferlist bl;
+ int r = ioctx.read("foo", bl, 1, 0);
+ ASSERT_TRUE(r == 1 || r == -ENOENT);
+ }
+
+ // cleanup
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+}
+
+TEST_F(LibRadosTwoPoolsPP, Whiteout) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create some whiteouts, verify they behave
+ {
+ ObjectWriteOperation op;
+ op.assert_exists();
+ op.remove();
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ {
+ ObjectWriteOperation op;
+ op.assert_exists();
+ op.remove();
+ ASSERT_EQ(-ENOENT, ioctx.operate("bar", &op));
+ }
+ {
+ ObjectWriteOperation op;
+ op.assert_exists();
+ op.remove();
+ ASSERT_EQ(-ENOENT, ioctx.operate("bar", &op));
+ }
+
+ // verify the whiteouts are there in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // delete a whiteout and verify it goes away
+ ASSERT_EQ(-ENOENT, ioctx.remove("foo"));
+ {
+ ObjectWriteOperation op;
+ op.remove();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // recreate an object and verify we can read it
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, WhiteoutDeleteCreate) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create an object
+ {
+ bufferlist bl;
+ bl.append("foo");
+ ASSERT_EQ(0, ioctx.write_full("foo", bl));
+ }
+
+ // do delete + create operation
+ {
+ ObjectWriteOperation op;
+ op.remove();
+ bufferlist bl;
+ bl.append("bar");
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // verify it still "exists" (w/ new content)
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('b', bl[0]);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, Evict) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger a promote
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ }
+
+ // read, trigger a whiteout, and a dirty object
+ {
+ bufferlist bl;
+ ASSERT_EQ(-ENOENT, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ(-ENOENT, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ(0, ioctx.write("bar", bl, bl.length(), 0));
+ }
+
+ // verify the object is present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // pin
+ {
+ ObjectWriteOperation op;
+ op.cache_pin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // evict the pinned object with -EPERM
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE,
+ NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EPERM, completion->get_return_value());
+ completion->release();
+ }
+
+ // unpin
+ {
+ ObjectWriteOperation op;
+ op.cache_unpin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify clean
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op, NULL));
+ ASSERT_FALSE(dirty);
+ ASSERT_EQ(0, r);
+ }
+
+ // evict
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE,
+ NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EBUSY, completion->get_return_value());
+ completion->release();
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, EvictSnap) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ ObjectWriteOperation op;
+ op.remove();
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger a promote on the head
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bam", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ // evict bam
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "bam", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ {
+ bufferlist bl;
+ ObjectReadOperation op;
+ op.read(1, 0, &bl, NULL);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "bam", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-ENOENT, completion->get_return_value());
+ completion->release();
+ }
+
+ // read foo snap
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ // evict foo snap
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // snap is gone...
+ {
+ bufferlist bl;
+ ObjectReadOperation op;
+ op.read(1, 0, &bl, NULL);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-ENOENT, completion->get_return_value());
+ completion->release();
+ }
+ // head is still there...
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ObjectReadOperation op;
+ op.read(1, 0, &bl, NULL);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // promote head + snap of bar
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ // evict bar head (fail)
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EBUSY, completion->get_return_value());
+ completion->release();
+ }
+
+ // evict bar snap
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // ...and then head
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ObjectReadOperation op;
+ op.read(1, 0, &bl, NULL);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // cleanup
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+}
+
+// this test case reproduces http://tracker.ceph.com/issues/8629
+TEST_F(LibRadosTwoPoolsPP, EvictSnap2) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger a promote on the head
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ // evict
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify the snapdir is not present in the cache pool
+ {
+ ObjectReadOperation op;
+ librados::snap_set_t snapset;
+ op.list_snaps(&snapset, NULL);
+ ioctx.snap_set_read(librados::SNAP_DIR);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-ENOENT, completion->get_return_value());
+ completion->release();
+ }
+}
+
+//This test case reproduces http://tracker.ceph.com/issues/17445
+TEST_F(LibRadosTwoPoolsPP, ListSnap){
+ SKIP_IF_CRIMSON();
+ // Create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // Create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ ObjectWriteOperation op;
+ op.remove();
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // Configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // Wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // Read, trigger a promote on the head
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ // Read foo snap
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ // Evict foo snap
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // Snap is gone...
+ {
+ bufferlist bl;
+ ObjectReadOperation op;
+ op.read(1, 0, &bl, NULL);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-ENOENT, completion->get_return_value());
+ completion->release();
+ }
+
+ // Do list-snaps
+ ioctx.snap_set_read(CEPH_SNAPDIR);
+ {
+ snap_set_t snap_set;
+ int snap_ret;
+ ObjectReadOperation op;
+ op.list_snaps(&snap_set, &snap_ret);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ 0, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, snap_ret);
+ ASSERT_LT(0u, snap_set.clones.size());
+ for (vector<librados::clone_info_t>::const_iterator r = snap_set.clones.begin();
+ r != snap_set.clones.end();
+ ++r) {
+ if (r->cloneid != librados::SNAP_HEAD) {
+ ASSERT_LT(0u, r->snaps.size());
+ }
+ }
+ }
+
+ // Cleanup
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+}
+
+// This test case reproduces https://tracker.ceph.com/issues/49409
+TEST_F(LibRadosTwoPoolsPP, EvictSnapRollbackReadRace) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ int len = string("hi there").length() * 2;
+ // append more chrunk data make sure the second promote
+ // op coming before the first promote op finished
+ for (int i=0; i<4*1024*1024/len; ++i)
+ bl.append("hi therehi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // create two snapshot, a clone
+ vector<uint64_t> my_snaps(2);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[1]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger a promote on the head
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ // try more times
+ int retries = 50;
+ for (int i=0; i<retries; ++i)
+ {
+ {
+ librados::AioCompletion * completion = cluster.aio_create_completion();
+ librados::AioCompletion * completion1 = cluster.aio_create_completion();
+
+ // send a snap rollback op and a snap read op parallel
+ // trigger two promote(copy) to the same snap clone obj
+ // the second snap read op is read-ordered make sure
+ // op not wait for objects_blocked_on_snap_promotion
+ ObjectWriteOperation op;
+ op.selfmanaged_snap_rollback(my_snaps[0]);
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op));
+
+ ioctx.snap_set_read(my_snaps[1]);
+ std::map<uint64_t, uint64_t> extents;
+ bufferlist read_bl;
+ int rval = -1;
+ ObjectReadOperation op1;
+ op1.sparse_read(0, 8, &extents, &read_bl, &rval);
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion1, &op1, &read_bl));
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+
+ completion1->wait_for_complete();
+ ASSERT_EQ(0, completion1->get_return_value());
+ completion1->release();
+ }
+
+ // evict foo snap
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ }
+
+ // cleanup
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+ ioctx.selfmanaged_snap_remove(my_snaps[1]);
+}
+
+TEST_F(LibRadosTwoPoolsPP, TryFlush) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // verify the object is present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // verify the object is NOT present in the base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+
+ // verify dirty
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op, NULL));
+ ASSERT_TRUE(dirty);
+ ASSERT_EQ(0, r);
+ }
+
+ // pin
+ {
+ ObjectWriteOperation op;
+ op.cache_pin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush the pinned object with -EPERM
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EPERM, completion->get_return_value());
+ completion->release();
+ }
+
+ // unpin
+ {
+ ObjectWriteOperation op;
+ op.cache_unpin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify clean
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op, NULL));
+ ASSERT_FALSE(dirty);
+ ASSERT_EQ(0, r);
+ }
+
+ // verify in base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it != ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+
+ // evict it
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op, librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify no longer in cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, Flush) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ uint64_t user_version = 0;
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // verify the object is present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // verify the object is NOT present in the base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+
+ // verify dirty
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op, NULL));
+ ASSERT_TRUE(dirty);
+ ASSERT_EQ(0, r);
+ user_version = cache_ioctx.get_last_version();
+ }
+
+ // pin
+ {
+ ObjectWriteOperation op;
+ op.cache_pin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush the pinned object with -EPERM
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EPERM, completion->get_return_value());
+ completion->release();
+ }
+
+ // unpin
+ {
+ ObjectWriteOperation op;
+ op.cache_unpin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify clean
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op, NULL));
+ ASSERT_FALSE(dirty);
+ ASSERT_EQ(0, r);
+ }
+
+ // verify in base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it != ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+
+ // evict it
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op, librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify no longer in cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // read it again and verify the version is consistent
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, cache_ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ(user_version, cache_ioctx.get_last_version());
+ }
+
+ // erase it
+ {
+ ObjectWriteOperation op;
+ op.remove();
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // flush whiteout
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // evict
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op, librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify no longer in cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+ // or base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, FlushSnap) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("a");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("b");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // and another
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("c");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // verify the object is present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // verify the object is NOT present in the base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+
+ // flush on head (should fail)
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EBUSY, completion->get_return_value());
+ completion->release();
+ }
+ // flush on recent snap (should fail)
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EBUSY, completion->get_return_value());
+ completion->release();
+ }
+ // flush on oldest snap
+ ioctx.snap_set_read(my_snaps[1]);
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // flush on next oldest snap
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // flush on head
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify i can read the snaps from the cache pool
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('b', bl[0]);
+ }
+ ioctx.snap_set_read(my_snaps[1]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('a', bl[0]);
+ }
+
+ // remove overlay
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // verify i can read the snaps from the base pool
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('b', bl[0]);
+ }
+ ioctx.snap_set_read(my_snaps[1]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('a', bl[0]);
+ }
+
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // cleanup
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+}
+
+TEST_F(LibRadosTierPP, FlushWriteRaces) {
+ SKIP_IF_CRIMSON();
+ Rados cluster;
+ std::string pool_name = get_temp_pool_name();
+ std::string cache_pool_name = pool_name + "-cache";
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+ ASSERT_EQ(0, cluster.pool_create(cache_pool_name.c_str()));
+ IoCtx cache_ioctx;
+ ASSERT_EQ(0, cluster.ioctx_create(cache_pool_name.c_str(), cache_ioctx));
+ cache_ioctx.application_enable("rados", true);
+ IoCtx ioctx;
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx));
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create/dirty object
+ bufferlist bl;
+ bl.append("hi there");
+ {
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // flush + write
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+
+ ObjectWriteOperation op2;
+ op2.write_full(bl);
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion2, &op2, 0));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ }
+
+ int tries = 1000;
+ do {
+ // create/dirty object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // try-flush + write
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ ObjectWriteOperation op2;
+ op2.write_full(bl);
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion2, &op2, 0));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ int r = completion->get_return_value();
+ ASSERT_TRUE(r == -EBUSY || r == 0);
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ if (r == -EBUSY)
+ break;
+ cout << "didn't get EBUSY, trying again" << std::endl;
+ }
+ ASSERT_TRUE(--tries);
+ } while (true);
+
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+
+ ASSERT_EQ(0, cluster.pool_delete(cache_pool_name.c_str()));
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster));
+}
+
+TEST_F(LibRadosTwoPoolsPP, FlushTryFlushRaces) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create/dirty object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // flush + flush
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+
+ ObjectReadOperation op2;
+ op2.cache_flush();
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion2, &op2,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ }
+
+ // create/dirty object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // flush + try-flush
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+
+ ObjectReadOperation op2;
+ op2.cache_try_flush();
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion2, &op2,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ }
+
+ // create/dirty object
+ int tries = 1000;
+ do {
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // try-flush + flush
+ // (flush will not piggyback on try-flush)
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ ObjectReadOperation op2;
+ op2.cache_flush();
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion2, &op2,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ int r = completion->get_return_value();
+ ASSERT_TRUE(r == -EBUSY || r == 0);
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ if (r == -EBUSY)
+ break;
+ cout << "didn't get EBUSY, trying again" << std::endl;
+ }
+ ASSERT_TRUE(--tries);
+ } while (true);
+
+ // create/dirty object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // try-flush + try-flush
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ ObjectReadOperation op2;
+ op2.cache_try_flush();
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion2, &op2,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ }
+}
+
+
+IoCtx *read_ioctx = 0;
+ceph::mutex test_lock = ceph::make_mutex("FlushReadRaces::lock");
+ceph::condition_variable cond;
+int max_reads = 100;
+int num_reads = 0; // in progress
+
+void flush_read_race_cb(completion_t cb, void *arg);
+
+void start_flush_read()
+{
+ //cout << " starting read" << std::endl;
+ ObjectReadOperation op;
+ op.stat(NULL, NULL, NULL);
+ librados::AioCompletion *completion = completions.getCompletion();
+ completion->set_complete_callback(0, flush_read_race_cb);
+ read_ioctx->aio_operate("foo", completion, &op, NULL);
+}
+
+void flush_read_race_cb(completion_t cb, void *arg)
+{
+ //cout << " finished read" << std::endl;
+ std::lock_guard l{test_lock};
+ if (num_reads > max_reads) {
+ num_reads--;
+ cond.notify_all();
+ } else {
+ start_flush_read();
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, TryFlushReadRace) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create/dirty object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ bufferptr bp(4000000); // make it big!
+ bp.zero();
+ bl.append(bp);
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // start a continuous stream of reads
+ read_ioctx = &ioctx;
+ test_lock.lock();
+ for (int i = 0; i < max_reads; ++i) {
+ start_flush_read();
+ num_reads++;
+ }
+ test_lock.unlock();
+
+ // try-flush
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+
+ // stop reads
+ std::unique_lock locker{test_lock};
+ max_reads = 0;
+ cond.wait(locker, [] { return num_reads == 0;});
+}
+
+TEST_F(LibRadosTierPP, HitSetNone) {
+ SKIP_IF_CRIMSON();
+ {
+ list< pair<time_t,time_t> > ls;
+ AioCompletion *c = librados::Rados::aio_create_completion();
+ ASSERT_EQ(0, ioctx.hit_set_list(123, c, &ls));
+ c->wait_for_complete();
+ ASSERT_EQ(0, c->get_return_value());
+ ASSERT_TRUE(ls.empty());
+ c->release();
+ }
+ {
+ bufferlist bl;
+ AioCompletion *c = librados::Rados::aio_create_completion();
+ ASSERT_EQ(0, ioctx.hit_set_get(123, c, 12345, &bl));
+ c->wait_for_complete();
+ ASSERT_EQ(-ENOENT, c->get_return_value());
+ c->release();
+ }
+}
+
+string set_pool_str(string pool, string var, string val)
+{
+ return string("{\"prefix\": \"osd pool set\",\"pool\":\"") + pool
+ + string("\",\"var\": \"") + var + string("\",\"val\": \"")
+ + val + string("\"}");
+}
+
+string set_pool_str(string pool, string var, int val)
+{
+ return string("{\"prefix\": \"osd pool set\",\"pool\":\"") + pool
+ + string("\",\"var\": \"") + var + string("\",\"val\": \"")
+ + stringify(val) + string("\"}");
+}
+
+TEST_F(LibRadosTwoPoolsPP, HitSetRead) {
+ SKIP_IF_CRIMSON();
+ // make it a tier
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+
+ // enable hitset tracking for this pool
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_count", 2),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_period", 600),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_type",
+ "explicit_object"),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ cache_ioctx.set_namespace("");
+
+ // keep reading until we see our object appear in the HitSet
+ utime_t start = ceph_clock_now();
+ utime_t hard_stop = start + utime_t(600, 0);
+
+ while (true) {
+ utime_t now = ceph_clock_now();
+ ASSERT_TRUE(now < hard_stop);
+
+ string name = "foo";
+ uint32_t hash;
+ ASSERT_EQ(0, cache_ioctx.get_object_hash_position2(name, &hash));
+ hobject_t oid(sobject_t(name, CEPH_NOSNAP), "", hash,
+ cluster.pool_lookup(cache_pool_name.c_str()), "");
+
+ bufferlist bl;
+ ASSERT_EQ(-ENOENT, cache_ioctx.read("foo", bl, 1, 0));
+
+ bufferlist hbl;
+ AioCompletion *c = librados::Rados::aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.hit_set_get(hash, c, now.sec(), &hbl));
+ c->wait_for_complete();
+ c->release();
+
+ if (hbl.length()) {
+ auto p = hbl.cbegin();
+ HitSet hs;
+ decode(hs, p);
+ if (hs.contains(oid)) {
+ cout << "ok, hit_set contains " << oid << std::endl;
+ break;
+ }
+ cout << "hmm, not in HitSet yet" << std::endl;
+ } else {
+ cout << "hmm, no HitSet yet" << std::endl;
+ }
+
+ sleep(1);
+ }
+}
+
+static int _get_pg_num(Rados& cluster, string pool_name)
+{
+ bufferlist inbl;
+ string cmd = string("{\"prefix\": \"osd pool get\",\"pool\":\"")
+ + pool_name
+ + string("\",\"var\": \"pg_num\",\"format\": \"json\"}");
+ bufferlist outbl;
+ int r = cluster.mon_command(cmd, inbl, &outbl, NULL);
+ ceph_assert(r >= 0);
+ string outstr(outbl.c_str(), outbl.length());
+ json_spirit::Value v;
+ if (!json_spirit::read(outstr, v)) {
+ cerr <<" unable to parse json " << outstr << std::endl;
+ return -1;
+ }
+
+ json_spirit::Object& o = v.get_obj();
+ for (json_spirit::Object::size_type i=0; i<o.size(); i++) {
+ json_spirit::Pair& p = o[i];
+ if (p.name_ == "pg_num") {
+ cout << "pg_num = " << p.value_.get_int() << std::endl;
+ return p.value_.get_int();
+ }
+ }
+ cerr << "didn't find pg_num in " << outstr << std::endl;
+ return -1;
+}
+
+int make_hitset(Rados& cluster, librados::IoCtx& cache_ioctx, int num_pg,
+ int num, std::map<int, HitSet>& hitsets, std::string& cache_pool_name)
+{
+ int pg = num_pg;
+ // do a bunch of writes
+ for (int i=0; i<num; ++i) {
+ bufferlist bl;
+ bl.append("a");
+ ceph_assert(0 == cache_ioctx.write(stringify(i), bl, 1, 0));
+ }
+
+ // get HitSets
+ for (int i=0; i<pg; ++i) {
+ list< pair<time_t,time_t> > ls;
+ AioCompletion *c = librados::Rados::aio_create_completion();
+ ceph_assert(0 == cache_ioctx.hit_set_list(i, c, &ls));
+ c->wait_for_complete();
+ c->release();
+ std::cout << "pg " << i << " ls " << ls << std::endl;
+ ceph_assert(!ls.empty());
+
+ // get the latest
+ c = librados::Rados::aio_create_completion();
+ bufferlist bl;
+ ceph_assert(0 == cache_ioctx.hit_set_get(i, c, ls.back().first, &bl));
+ c->wait_for_complete();
+ c->release();
+
+ try {
+ auto p = bl.cbegin();
+ decode(hitsets[i], p);
+ }
+ catch (buffer::error& e) {
+ std::cout << "failed to decode hit set; bl len is " << bl.length() << "\n";
+ bl.hexdump(std::cout);
+ std::cout << std::endl;
+ throw e;
+ }
+
+ // cope with racing splits by refreshing pg_num
+ if (i == pg - 1)
+ pg = _get_pg_num(cluster, cache_pool_name);
+ }
+ return pg;
+}
+
+TEST_F(LibRadosTwoPoolsPP, HitSetWrite) {
+ SKIP_IF_CRIMSON();
+ int num_pg = _get_pg_num(cluster, pool_name);
+ ceph_assert(num_pg > 0);
+
+ // make it a tier
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+
+ // enable hitset tracking for this pool
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_count", 8),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_period", 600),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_type",
+ "explicit_hash"),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ cache_ioctx.set_namespace("");
+
+ int num = 200;
+
+ std::map<int,HitSet> hitsets;
+
+ num_pg = make_hitset(cluster, cache_ioctx, num_pg, num, hitsets, cache_pool_name);
+
+ int retry = 0;
+
+ for (int i=0; i<num; ++i) {
+ string n = stringify(i);
+ uint32_t hash;
+ ASSERT_EQ(0, cache_ioctx.get_object_hash_position2(n, &hash));
+ hobject_t oid(sobject_t(n, CEPH_NOSNAP), "", hash,
+ cluster.pool_lookup(cache_pool_name.c_str()), "");
+ std::cout << "checking for " << oid << std::endl;
+ bool found = false;
+ for (int p=0; p<num_pg; ++p) {
+ if (hitsets[p].contains(oid)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found && retry < 5) {
+ num_pg = make_hitset(cluster, cache_ioctx, num_pg, num, hitsets, cache_pool_name);
+ i--;
+ retry++;
+ continue;
+ }
+ ASSERT_TRUE(found);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, HitSetTrim) {
+ SKIP_IF_CRIMSON();
+ unsigned count = 3;
+ unsigned period = 3;
+
+ // make it a tier
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+
+ // enable hitset tracking for this pool
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_count", count),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_period", period),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_type", "bloom"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_fpp", ".01"),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ cache_ioctx.set_namespace("");
+
+ // do a bunch of writes and make sure the hitsets rotate
+ utime_t start = ceph_clock_now();
+ utime_t hard_stop = start + utime_t(count * period * 50, 0);
+
+ time_t first = 0;
+ while (true) {
+ string name = "foo";
+ uint32_t hash;
+ ASSERT_EQ(0, cache_ioctx.get_object_hash_position2(name, &hash));
+ hobject_t oid(sobject_t(name, CEPH_NOSNAP), "", hash, -1, "");
+
+ bufferlist bl;
+ bl.append("f");
+ ASSERT_EQ(0, cache_ioctx.write("foo", bl, 1, 0));
+
+ list<pair<time_t, time_t> > ls;
+ AioCompletion *c = librados::Rados::aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.hit_set_list(hash, c, &ls));
+ c->wait_for_complete();
+ c->release();
+
+ cout << " got ls " << ls << std::endl;
+ if (!ls.empty()) {
+ if (!first) {
+ first = ls.front().first;
+ cout << "first is " << first << std::endl;
+ } else {
+ if (ls.front().first != first) {
+ cout << "first now " << ls.front().first << ", trimmed" << std::endl;
+ break;
+ }
+ }
+ }
+
+ utime_t now = ceph_clock_now();
+ ASSERT_TRUE(now < hard_stop);
+
+ sleep(1);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, PromoteOn2ndRead) {
+ SKIP_IF_CRIMSON();
+ // create object
+ for (int i=0; i<20; ++i) {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo" + stringify(i), &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // enable hitset tracking for this pool
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_count", 2),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_period", 600),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_type", "bloom"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "min_read_recency_for_promote", 1),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_grade_decay_rate", 20),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_search_last_n", 1),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ int fake = 0; // set this to non-zero to test spurious promotion,
+ // e.g. from thrashing
+ int attempt = 0;
+ string obj;
+ while (true) {
+ // 1st read, don't trigger a promote
+ obj = "foo" + stringify(attempt);
+ cout << obj << std::endl;
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read(obj.c_str(), bl, 1, 0));
+ if (--fake >= 0) {
+ sleep(1);
+ ASSERT_EQ(1, ioctx.read(obj.c_str(), bl, 1, 0));
+ sleep(1);
+ }
+ }
+
+ // verify the object is NOT present in the cache tier
+ {
+ bool found = false;
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ while (it != cache_ioctx.nobjects_end()) {
+ cout << " see " << it->get_oid() << std::endl;
+ if (it->get_oid() == string(obj.c_str())) {
+ found = true;
+ break;
+ }
+ ++it;
+ }
+ if (!found)
+ break;
+ }
+
+ ++attempt;
+ ASSERT_LE(attempt, 20);
+ cout << "hrm, object is present in cache on attempt " << attempt
+ << ", retrying" << std::endl;
+ }
+
+ // Read until the object is present in the cache tier
+ cout << "verifying " << obj << " is eventually promoted" << std::endl;
+ while (true) {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read(obj.c_str(), bl, 1, 0));
+
+ bool there = false;
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ while (it != cache_ioctx.nobjects_end()) {
+ if (it->get_oid() == string(obj.c_str())) {
+ there = true;
+ break;
+ }
+ ++it;
+ }
+ if (there)
+ break;
+
+ sleep(1);
+ }
+
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsPP, ProxyRead) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"readproxy\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read and verify the object
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ // Verify 10 times the object is NOT present in the cache tier
+ uint32_t i = 0;
+ while (i++ < 10) {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ sleep(1);
+ }
+
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsPP, CachePin) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger promote
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ(1, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ(1, ioctx.read("baz", bl, 1, 0));
+ ASSERT_EQ(1, ioctx.read("bam", bl, 1, 0));
+ }
+
+ // verify the objects are present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ for (uint32_t i = 0; i < 4; i++) {
+ ASSERT_TRUE(it->get_oid() == string("foo") ||
+ it->get_oid() == string("bar") ||
+ it->get_oid() == string("baz") ||
+ it->get_oid() == string("bam"));
+ ++it;
+ }
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // pin objects
+ {
+ ObjectWriteOperation op;
+ op.cache_pin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ {
+ ObjectWriteOperation op;
+ op.cache_pin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("baz", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // enable agent
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_count", 2),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_period", 600),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_type", "bloom"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "min_read_recency_for_promote", 1),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "target_max_objects", 1),
+ inbl, NULL, NULL));
+
+ sleep(10);
+
+ // Verify the pinned object 'foo' is not flushed/evicted
+ uint32_t count = 0;
+ while (true) {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("baz", bl, 1, 0));
+
+ count = 0;
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ while (it != cache_ioctx.nobjects_end()) {
+ ASSERT_TRUE(it->get_oid() == string("foo") ||
+ it->get_oid() == string("bar") ||
+ it->get_oid() == string("baz") ||
+ it->get_oid() == string("bam"));
+ ++count;
+ ++it;
+ }
+ if (count == 2) {
+ ASSERT_TRUE(it->get_oid() == string("foo") ||
+ it->get_oid() == string("baz"));
+ break;
+ }
+
+ sleep(1);
+ }
+
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsPP, SetRedirectRead) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ {
+ ObjectWriteOperation op;
+ op.set_redirect("bar", cache_ioctx, 0);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // read and verify the object
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('t', bl[0]);
+ }
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestPromoteRead) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet mimic
+ if (_get_required_osd_release(cluster) < "mimic") {
+ GTEST_SKIP() << "cluster is not yet mimic, skipping test";
+ }
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("base chunk");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo-chunk", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("CHUNK");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar-chunk", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // set-redirect
+ {
+ ObjectWriteOperation op;
+ op.set_redirect("bar", cache_ioctx, 0);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 0, 2, "bar-chunk", "foo-chunk");
+
+ // promote
+ {
+ ObjectWriteOperation op;
+ op.tier_promote();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // read and verify the object (redirect)
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('t', bl[0]);
+ }
+ // promote
+ {
+ ObjectWriteOperation op;
+ op.tier_promote();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo-chunk", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // read and verify the object
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo-chunk", bl, 1, 0));
+ ASSERT_EQ('C', bl[0]);
+ }
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestRefRead) {
+ SKIP_IF_CRIMSON();
+ // note: require >= mimic
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("base chunk");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo-chunk", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("CHUNK");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar-chunk", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // set-redirect
+ {
+ ObjectWriteOperation op;
+ op.set_redirect("bar", cache_ioctx, 0, CEPH_OSD_OP_FLAG_WITH_REFERENCE);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // set-chunk
+ {
+ ObjectReadOperation op;
+ op.set_chunk(0, 2, cache_ioctx, "bar-chunk", 0, CEPH_OSD_OP_FLAG_WITH_REFERENCE);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo-chunk", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // redirect's refcount
+ {
+ bufferlist t;
+ cache_ioctx.getxattr("bar", CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_EQ(1U, refs.count());
+ }
+ // chunk's refcount
+ {
+ bufferlist t;
+ cache_ioctx.getxattr("bar-chunk", CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_EQ(1u, refs.count());
+ }
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestUnset) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet nautilus
+ if (_get_required_osd_release(cluster) < "nautilus") {
+ GTEST_SKIP() << "cluster is not yet nautilus, skipping test";
+ }
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("base chunk");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo-chunk", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("CHUNK");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar-chunk", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // set-redirect
+ {
+ ObjectWriteOperation op;
+ op.set_redirect("bar", cache_ioctx, 0, CEPH_OSD_OP_FLAG_WITH_REFERENCE);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // set-chunk
+ {
+ ObjectReadOperation op;
+ op.set_chunk(0, 2, cache_ioctx, "bar-chunk", 0, CEPH_OSD_OP_FLAG_WITH_REFERENCE);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo-chunk", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // redirect's refcount
+ {
+ bufferlist t;
+ cache_ioctx.getxattr("bar", CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_EQ(1u, refs.count());
+ }
+ // chunk's refcount
+ {
+ bufferlist t;
+ cache_ioctx.getxattr("bar-chunk", CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_EQ(1u, refs.count());
+ }
+
+ // unset-manifest for set-redirect
+ {
+ ObjectWriteOperation op;
+ op.unset_manifest();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // unset-manifest for set-chunk
+ {
+ ObjectWriteOperation op;
+ op.unset_manifest();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo-chunk", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // redirect's refcount
+ {
+ bufferlist t;
+ cache_ioctx.getxattr("bar-chunk", CHUNK_REFCOUNT_ATTR, t);
+ if (t.length() != 0U) {
+ ObjectWriteOperation op;
+ op.unset_manifest();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EOPNOTSUPP, completion->get_return_value());
+ completion->release();
+ }
+ }
+ // chunk's refcount
+ {
+ bufferlist t;
+ cache_ioctx.getxattr("bar-chunk", CHUNK_REFCOUNT_ATTR, t);
+ if (t.length() != 0U) {
+ ObjectWriteOperation op;
+ op.unset_manifest();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo-chunk", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EOPNOTSUPP, completion->get_return_value());
+ completion->release();
+ }
+ }
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestDedupRefRead) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet nautilus
+ if (_get_required_osd_release(cluster) < "nautilus") {
+ GTEST_SKIP() << "cluster is not yet nautilus, skipping test";
+ }
+
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(pool_name, "fingerprint_algorithm", "sha1"),
+ inbl, NULL, NULL));
+ cluster.wait_for_latest_osdmap();
+ string tgt_oid;
+
+ // get fp_oid
+ tgt_oid = get_fp_oid("There hi", "sha1");
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("There hi");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("There hi");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo-dedup", &op));
+ }
+
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("There hi");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(tgt_oid, &op));
+ }
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 0, 8, tgt_oid, "foo-dedup");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 0, 8, tgt_oid, "foo");
+ // chunk's refcount
+ {
+ bufferlist t;
+ cache_ioctx.getxattr(tgt_oid, CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_LE(2u, refs.count());
+ }
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestSnapRefcount) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ cout << "cluster is not yet octopus, skipping test" << std::endl;
+ return;
+ }
+
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(pool_name, "fingerprint_algorithm", "sha1"),
+ inbl, NULL, NULL));
+ cluster.wait_for_latest_osdmap();
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("there hi");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hi");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ string er_fp_oid, hi_fp_oid, bb_fp_oid;
+
+ // get fp_oid
+ er_fp_oid = get_fp_oid("er", "sha1");
+ hi_fp_oid = get_fp_oid("hi", "sha1");
+ bb_fp_oid = get_fp_oid("bb", "sha1");
+
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("er");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(er_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("hi");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(hi_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("bb");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(bb_fp_oid, &op));
+ }
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, er_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, hi_fp_oid, "foo");
+
+ // make all chunks dirty --> flush
+ // foo: [er] [hi]
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("er");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"er", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ cache_ioctx.getxattr(p_str, CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_LE(1u, refs.count());
+ }
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ // foo: [bb] [hi]
+ // create a clone
+ {
+ bufferlist bl;
+ bl.append("Thbbe");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+ // make clean
+ {
+ bufferlist bl;
+ bl.append("Thbbe");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, bb_fp_oid, "foo");
+
+ // and another
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ // foo: [er] [hi]
+ // create a clone
+ {
+ bufferlist bl;
+ bl.append("There");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+ // make clean
+ {
+ bufferlist bl;
+ bl.append("There");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, er_fp_oid, "foo");
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("er");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"er", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ cache_ioctx.getxattr(p_str, CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_LE(2u, refs.count());
+ }
+
+ // and another
+ my_snaps.resize(3);
+ my_snaps[2] = my_snaps[1];
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ // foo: [bb] [hi]
+ // create a clone
+ {
+ bufferlist bl;
+ bl.append("Thbbe");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+ // make clean
+ {
+ bufferlist bl;
+ bl.append("Thbbe");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, bb_fp_oid, "foo");
+
+ /*
+ * snap[2]: [er] [hi]
+ * snap[1]: [bb] [hi]
+ * snap[0]: [er] [hi]
+ * head: [bb] [hi]
+ */
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("hi");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"hi", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, p_str, 1);
+ }
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("er");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"er", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ cache_ioctx.getxattr(p_str, CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_LE(2u, refs.count());
+ }
+
+ // remove snap
+ ioctx.selfmanaged_snap_remove(my_snaps[2]);
+
+ /*
+ * snap[1]: [bb] [hi]
+ * snap[0]: [er] [hi]
+ * head: [bb] [hi]
+ */
+
+ sleep(10);
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("hi");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"hi", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, p_str, 1);
+ }
+
+ // remove snap
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+
+ /*
+ * snap[1]: [bb] [hi]
+ * head: [bb] [hi]
+ */
+
+ sleep(10);
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("bb");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"bb", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, p_str, 1);
+ }
+
+ // remove snap
+ ioctx.selfmanaged_snap_remove(my_snaps[1]);
+
+ /*
+ * snap[1]: [bb] [hi]
+ */
+
+ sleep(10);
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("bb");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"bb", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, p_str, 1);
+ }
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("hi");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"hi", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, p_str, 1);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestSnapRefcount2) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ cout << "cluster is not yet octopus, skipping test" << std::endl;
+ return;
+ }
+
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(pool_name, "fingerprint_algorithm", "sha1"),
+ inbl, NULL, NULL));
+ cluster.wait_for_latest_osdmap();
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("Thabe cdHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar", &op));
+ }
+
+ string ab_fp_oid, cd_fp_oid, ef_fp_oid, BB_fp_oid;
+
+ // get fp_oid
+ ab_fp_oid = get_fp_oid("ab", "sha1");
+ cd_fp_oid = get_fp_oid("cd", "sha1");
+ ef_fp_oid = get_fp_oid("ef", "sha1");
+ BB_fp_oid = get_fp_oid("BB", "sha1");
+
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("ab");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(ab_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("cd");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(cd_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("ef");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(ef_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("BB");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(BB_fp_oid, &op));
+ }
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, ab_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, cd_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, ef_fp_oid, "foo");
+
+
+ // make all chunks dirty --> flush
+ // foo: [ab] [cd] [ef]
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ // foo: [BB] [BB] [ef]
+ // create a clone
+ {
+ bufferlist bl;
+ bl.append("ThBBe BB");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+ // make clean
+ {
+ bufferlist bl;
+ bl.append("ThBBe BB");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, BB_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, BB_fp_oid, "foo");
+
+ // and another
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ // foo: [ab] [cd] [ef]
+ // create a clone
+ {
+ bufferlist bl;
+ bl.append("Thabe cd");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+ // make clean
+ {
+ bufferlist bl;
+ bl.append("Thabe cd");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, ab_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, cd_fp_oid, "foo");
+
+ /*
+ * snap[1]: [ab] [cd] [ef]
+ * snap[0]: [BB] [BB] [ef]
+ * head: [ab] [cd] [ef]
+ */
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("ab");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"ab", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ cache_ioctx.getxattr(p_str, CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_LE(2u, refs.count());
+ }
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("cd");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"cd", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ cache_ioctx.getxattr(p_str, CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_LE(2u, refs.count());
+ }
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("BB");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"BB", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ cache_ioctx.getxattr(p_str, CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_LE(2u, refs.count());
+ }
+
+ // remove snap
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+
+ /*
+ * snap[1]: [ab] [cd] [ef]
+ * head: [ab] [cd] [ef]
+ */
+
+ sleep(10);
+
+ // check chunk's refcount
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("BB");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"BB", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, p_str, 0);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestTestSnapCreate) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ GTEST_SKIP() << "cluster is not yet octopus, skipping test";
+ }
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("base chunk");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("CHUNKS CHUNKS");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar", &op));
+ }
+
+ string ba_fp_oid, se_fp_oid, ch_fp_oid;
+
+ // get fp_oid
+ ba_fp_oid = get_fp_oid("ba", "sha1");
+ se_fp_oid = get_fp_oid("se", "sha1");
+ ch_fp_oid = get_fp_oid("ch", "sha1");
+
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("ba");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(ba_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("se");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(se_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("ch");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(ch_fp_oid, &op));
+ }
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 0, 2, ba_fp_oid, "foo");
+
+ // try to create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, se_fp_oid, "foo");
+
+ // check whether clone is created
+ ioctx.snap_set_read(librados::SNAP_DIR);
+ {
+ snap_set_t snap_set;
+ int snap_ret;
+ ObjectReadOperation op;
+ op.list_snaps(&snap_set, &snap_ret);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ 0, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, snap_ret);
+ ASSERT_LT(0u, snap_set.clones.size());
+ ASSERT_EQ(1, snap_set.clones.size());
+ }
+
+ // create a clone
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ bl.append("B");
+ ASSERT_EQ(0, ioctx.write("foo", bl, 1, 0));
+ }
+
+ ioctx.snap_set_read(my_snaps[0]);
+ // set-chunk to clone
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, ch_fp_oid, "foo");
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestRedirectAfterPromote) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ GTEST_SKIP() << "cluster is not yet octopus, skipping test";
+ }
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("base chunk");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("BASE CHUNK");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar", &op));
+ }
+
+ // set-redirect
+ {
+ ObjectWriteOperation op;
+ op.set_redirect("bar", cache_ioctx, 0);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // promote
+ {
+ ObjectWriteOperation op;
+ op.tier_promote();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // write
+ {
+ bufferlist bl;
+ bl.append("a");
+ ASSERT_EQ(0, ioctx.write("foo", bl, 1, 0));
+ }
+
+ // read and verify the object (redirect)
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('a', bl[0]);
+ }
+
+ // read and verify the object (redirect)
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, cache_ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ('B', bl[0]);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestCheckRefcountWhenModification) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ GTEST_SKIP() << "cluster is not yet octopus, skipping test";
+ }
+
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(pool_name, "fingerprint_algorithm", "sha1"),
+ inbl, NULL, NULL));
+ cluster.wait_for_latest_osdmap();
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ string er_fp_oid, hi_fp_oid, HI_fp_oid, ai_fp_oid, bi_fp_oid,
+ Er_fp_oid, Hi_fp_oid, Si_fp_oid;
+
+ // get fp_oid
+ er_fp_oid = get_fp_oid("er", "sha1");
+ hi_fp_oid = get_fp_oid("hi", "sha1");
+ HI_fp_oid = get_fp_oid("HI", "sha1");
+ ai_fp_oid = get_fp_oid("ai", "sha1");
+ bi_fp_oid = get_fp_oid("bi", "sha1");
+ Er_fp_oid = get_fp_oid("Er", "sha1");
+ Hi_fp_oid = get_fp_oid("Hi", "sha1");
+ Si_fp_oid = get_fp_oid("Si", "sha1");
+
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("er");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(er_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("hi");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(hi_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("HI");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(HI_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("ai");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(ai_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("bi");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(bi_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("Er");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(Er_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("Hi");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(Hi_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("Si");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(Si_fp_oid, &op));
+ }
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, er_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, hi_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, HI_fp_oid, "foo");
+
+ // foo head: [er] [hi] [HI]
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+
+ // foo snap[0]: [er] [hi] [HI]
+ // foo head : [er] [ai] [HI]
+ // create a clone
+ {
+ bufferlist bl;
+ bl.append("a");
+ ASSERT_EQ(0, ioctx.write("foo", bl, 1, 6));
+ }
+ // write
+ {
+ bufferlist bl;
+ bl.append("a");
+ ASSERT_EQ(0, ioctx.write("foo", bl, 1, 6));
+ }
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, ai_fp_oid, "foo");
+
+ // foo snap[0]: [er] [hi] [HI]
+ // foo head : [er] [bi] [HI]
+ // create a clone
+ {
+ bufferlist bl;
+ bl.append("b");
+ ASSERT_EQ(0, ioctx.write("foo", bl, 1, 6));
+ }
+ // write
+ {
+ bufferlist bl;
+ bl.append("b");
+ ASSERT_EQ(0, ioctx.write("foo", bl, 1, 6));
+ }
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, bi_fp_oid, "foo");
+
+ sleep(10);
+
+ // check chunk's refcount
+ // [ai]'s refcount should be 0
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("ai");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"ai", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, p_str, 0);
+ }
+
+ // foo snap[0]: [er] [hi] [HI]
+ // foo head : [Er] [Hi] [Si]
+ // create a clone
+ {
+ bufferlist bl;
+ bl.append("thEre HiSi");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ // write
+ {
+ bufferlist bl;
+ bl.append("thEre HiSi");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, Er_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, Hi_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, Si_fp_oid, "foo");
+
+ // foo snap[0]: [er] [hi] [HI]
+ // foo head : [ER] [HI] [SI]
+ // write
+ {
+ bufferlist bl;
+ bl.append("thERe HISI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ sleep(10);
+
+ // check chunk's refcount
+ // [Er]'s refcount should be 0
+ {
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = strlen("Er");
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1];
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ sha1_gen.Update((const unsigned char *)"Er", size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, p_str, 0);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestSnapIncCount) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ cout << "cluster is not yet octopus, skipping test" << std::endl;
+ return;
+ }
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk1", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk2", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk3", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk4", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, "chunk1", "foo");
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, "chunk4", "foo");
+ // foo snap[1]:
+ // foo snap[0]:
+ // foo head : [chunk1] [chunk4]
+
+ ioctx.snap_set_read(my_snaps[1]);
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, "chunk2", "foo");
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, "chunk4", "foo");
+ // foo snap[1]: [chunk2] [chunk4]
+ // foo snap[0]:
+ // foo head : [chunk1] [chunk4]
+
+ ioctx.snap_set_read(my_snaps[0]);
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, "chunk2", "foo");
+ // foo snap[1]: [chunk2] [chunk4]
+ // foo snap[0]: [chunk2]
+ // foo head : [chunk1] [chunk4]
+
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, "chunk3", "foo");
+ // foo snap[1]: [chunk2] [chunk4]
+ // foo snap[0]: [chunk3] [chunk2]
+ // foo head : [chunk1] [chunk4]
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, "chunk4", "foo");
+ // foo snap[1]: [chunk2] [chunk4]
+ // foo snap[0]: [chunk3] [chunk2] [chunk4]
+ // foo head : [chunk1] [chunk4]
+
+ // check chunk's refcount
+ check_fp_oid_refcount(cache_ioctx, "chunk1", 1u, "");
+
+ // check chunk's refcount
+ check_fp_oid_refcount(cache_ioctx, "chunk2", 1u, "");
+
+ // check chunk's refcount
+ check_fp_oid_refcount(cache_ioctx, "chunk3", 1u, "");
+ sleep(10);
+
+ // check chunk's refcount
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk4", 1);
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestEvict) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ cout << "cluster is not yet octopus, skipping test" << std::endl;
+ return;
+ }
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk1", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk2", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk3", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk4", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, "chunk1", "foo");
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, "chunk4", "foo");
+ // foo snap[1]:
+ // foo snap[0]:
+ // foo head : [chunk1] [chunk4]
+
+ ioctx.snap_set_read(my_snaps[1]);
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 0, 10, "chunk2", "foo");
+ // foo snap[1]: [ chunk2 ]
+ // foo snap[0]:
+ // foo head : [chunk1] [chunk4]
+
+ ioctx.snap_set_read(my_snaps[0]);
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, "chunk2", "foo");
+ // foo snap[1]: [ chunk2 ]
+ // foo snap[0]: [chunk2]
+ // foo head : [chunk1] [chunk4]
+
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, "chunk3", "foo");
+ // foo snap[1]: [ chunk2 ]
+ // foo snap[0]: [chunk3] [chunk2]
+ // foo head : [chunk1] [chunk4]
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, "chunk4", "foo");
+ // foo snap[1]: [ chunk2 ]
+ // foo snap[0]: [chunk3] [chunk2] [chunk4]
+ // foo head : [chunk1] [chunk4]
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 0, 2, "chunk4", "foo");
+ // foo snap[1]: [ chunk2 ]
+ // foo snap[0]: [chunk4] [chunk3] [chunk2] [chunk4]
+ // foo head : [chunk1] [chunk4]
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 4, 2, "chunk1", "foo");
+ // foo snap[1]: [ chunk2 ]
+ // foo snap[0]: [chunk4] [chunk3] [chunk1] [chunk2] [chunk4]
+ // foo head : [chunk1] [chunk4]
+
+ {
+ ObjectReadOperation op, stat_op;
+ uint64_t size;
+ op.tier_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+
+ stat_op.stat(&size, NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &stat_op, NULL));
+ ASSERT_EQ(10, size);
+ }
+
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ ObjectReadOperation op, stat_op;
+ uint64_t size;
+ op.tier_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+
+ stat_op.stat(&size, NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &stat_op, NULL));
+ ASSERT_EQ(strlen("there hiHI"), size);
+ }
+
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestEvictPromote) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ cout << "cluster is not yet octopus, skipping test" << std::endl;
+ return;
+ }
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("EREHT hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk1", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk2", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("THERE HIHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk3", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("there");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 0, 2, "chunk1", "foo");
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, "chunk2", "foo");
+ // foo snap[0]:
+ // foo head : [chunk1] [chunk2]
+
+ ioctx.snap_set_read(my_snaps[0]);
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 0, 10, "chunk3", "foo");
+ // foo snap[0]: [ chunk3 ]
+ // foo head : [chunk1] [chunk2]
+
+
+ {
+ ObjectReadOperation op, stat_op;
+ uint64_t size;
+ op.tier_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+
+ stat_op.stat(&size, NULL, NULL);
+ ASSERT_EQ(0, ioctx.operate("foo", &stat_op, NULL));
+ ASSERT_EQ(10, size);
+
+ }
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('T', bl[0]);
+ }
+
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ASSERT_EQ(10, ioctx.read("foo", bl, 10, 0));
+ ASSERT_EQ('H', bl[8]);
+ }
+}
+
+
+TEST_F(LibRadosTwoPoolsPP, ManifestSnapSizeMismatch) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ cout << "cluster is not yet octopus, skipping test" << std::endl;
+ return;
+ }
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("chunk1", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there HIHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("chunk2", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("There hiHI");
+ ASSERT_EQ(0, cache_ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("tHere hiHI");
+ ASSERT_EQ(0, cache_ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ // set-chunk
+ manifest_set_chunk(cluster, ioctx, cache_ioctx, 0, 10, "chunk1", "foo");
+
+ cache_ioctx.snap_set_read(my_snaps[1]);
+
+ // set-chunk
+ manifest_set_chunk(cluster, ioctx, cache_ioctx, 0, 10, "chunk2", "foo");
+
+ // evict
+ {
+ ObjectReadOperation op, stat_op;
+ op.tier_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ }
+
+ uint32_t hash;
+ ASSERT_EQ(0, cache_ioctx.get_object_pg_hash_position2("foo", &hash));
+
+ // scrub
+ {
+ for (int tries = 0; tries < 5; ++tries) {
+ bufferlist inbl;
+ ostringstream ss;
+ ss << "{\"prefix\": \"pg deep-scrub\", \"pgid\": \""
+ << cache_ioctx.get_id() << "."
+ << std::hex << hash
+ << "\"}";
+ int r = cluster.mon_command(ss.str(), inbl, NULL, NULL);
+ if (r == -ENOENT ||
+ r == -EAGAIN) {
+ sleep(5);
+ continue;
+ }
+ ASSERT_EQ(0, r);
+ break;
+ }
+ cout << "waiting for scrubs..." << std::endl;
+ sleep(20);
+ cout << "done waiting" << std::endl;
+ }
+
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, cache_ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('t', bl[0]);
+ }
+}
+
+#include <common/CDC.h>
+TEST_F(LibRadosTwoPoolsPP, DedupFlushRead) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ GTEST_SKIP() << "cluster is not yet octopus, skipping test";
+ }
+
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "fingerprint_algorithm", "sha1"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_tier", pool_name),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_chunk_algorithm", "fastcdc"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_cdc_chunk_size", 1024),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create object
+ bufferlist gbl;
+ {
+ generate_buffer(1024*8, &gbl);
+ ObjectWriteOperation op;
+ op.write_full(gbl);
+ ASSERT_EQ(0, cache_ioctx.operate("foo-chunk", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("DDse chunk");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar-chunk", &op));
+ }
+
+ // set-chunk to set manifest object
+ {
+ ObjectReadOperation op;
+ op.set_chunk(0, 2, ioctx, "bar-chunk", 0,
+ CEPH_OSD_OP_FLAG_WITH_REFERENCE);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo-chunk", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // flush
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo-chunk", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ std::unique_ptr<CDC> cdc = CDC::create("fastcdc", cbits(1024)-1);
+ vector<pair<uint64_t, uint64_t>> chunks;
+ bufferlist chunk;
+ cdc->calc_chunks(gbl, &chunks);
+ chunk.substr_of(gbl, chunks[1].first, chunks[1].second);
+ string tgt_oid;
+ {
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1] = {0};
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ SHA1 sha1_gen;
+ int size = chunk.length();
+ sha1_gen.Update((const unsigned char *)chunk.c_str(), size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ tgt_oid = string(p_str);
+ }
+
+ // read and verify the chunked object
+ {
+ bufferlist test_bl;
+ ASSERT_EQ(2, ioctx.read(tgt_oid, test_bl, 2, 0));
+ ASSERT_EQ(test_bl[1], chunk[1]);
+ }
+
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_cdc_chunk_size", 512),
+ inbl, NULL, NULL));
+ cluster.wait_for_latest_osdmap();
+
+ // make a dirty chunks
+ {
+ bufferlist bl;
+ bl.append("hi");
+ ASSERT_EQ(0, cache_ioctx.write("foo-chunk", bl, bl.length(), 0));
+ }
+
+ // flush
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo-chunk", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ cdc = CDC::create("fastcdc", cbits(512)-1);
+ chunks.clear();
+ cdc->calc_chunks(gbl, &chunks);
+ bufferlist chunk_512;
+ chunk_512.substr_of(gbl, chunks[3].first, chunks[3].second);
+ {
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1] = {0};
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ SHA1 sha1_gen;
+ int size = chunk_512.length();
+ sha1_gen.Update((const unsigned char *)chunk_512.c_str(), size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ tgt_oid = string(p_str);
+ }
+
+ // read and verify the chunked object
+ {
+ bufferlist test_bl;
+ ASSERT_EQ(2, ioctx.read(tgt_oid, test_bl, 2, 0));
+ ASSERT_EQ(test_bl[1], chunk_512[1]);
+ }
+
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_cdc_chunk_size", 16384),
+ inbl, NULL, NULL));
+ cluster.wait_for_latest_osdmap();
+
+ // make a dirty chunks
+ {
+ bufferlist bl;
+ bl.append("hi");
+ ASSERT_EQ(0, cache_ioctx.write("foo-chunk", bl, bl.length(), 0));
+ gbl.begin(0).copy_in(bl.length(), bl);
+ }
+ // flush
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo-chunk", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ cdc = CDC::create("fastcdc", cbits(16384)-1);
+ chunks.clear();
+ cdc->calc_chunks(gbl, &chunks);
+ bufferlist chunk_16384;
+ chunk_16384.substr_of(gbl, chunks[0].first, chunks[0].second);
+ {
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1] = {0};
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ SHA1 sha1_gen;
+ int size = chunk_16384.length();
+ sha1_gen.Update((const unsigned char *)chunk_16384.c_str(), size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ tgt_oid = string(p_str);
+ }
+ // read and verify the chunked object
+ {
+ bufferlist test_bl;
+ ASSERT_EQ(2, ioctx.read(tgt_oid, test_bl, 2, 0));
+ ASSERT_EQ(test_bl[0], chunk_16384[0]);
+ }
+
+ // less than object size
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_cdc_chunk_size", 1024),
+ inbl, NULL, NULL));
+ cluster.wait_for_latest_osdmap();
+
+ // make a dirty chunks
+ // a chunk_info is deleted by write, which converts the manifest object to non-manifest object
+ {
+ bufferlist bl;
+ bl.append("hi");
+ ASSERT_EQ(0, cache_ioctx.write("foo-chunk", bl, bl.length(), 0));
+ }
+
+ // reset set-chunk
+ {
+ bufferlist bl;
+ bl.append("DDse chunk");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar-chunk", &op));
+ }
+ // set-chunk to set manifest object
+ {
+ ObjectReadOperation op;
+ op.set_chunk(0, 2, ioctx, "bar-chunk", 0,
+ CEPH_OSD_OP_FLAG_WITH_REFERENCE);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo-chunk", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // flush
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo-chunk", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ cdc = CDC::create("fastcdc", cbits(1024)-1);
+ chunks.clear();
+ cdc->calc_chunks(gbl, &chunks);
+ bufferlist small_chunk;
+ small_chunk.substr_of(gbl, chunks[1].first, chunks[1].second);
+ {
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1] = {0};
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ SHA1 sha1_gen;
+ int size = small_chunk.length();
+ sha1_gen.Update((const unsigned char *)small_chunk.c_str(), size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ tgt_oid = string(p_str);
+ }
+ // read and verify the chunked object
+ {
+ bufferlist test_bl;
+ ASSERT_EQ(2, ioctx.read(tgt_oid, test_bl, 2, 0));
+ ASSERT_EQ(test_bl[0], small_chunk[0]);
+ }
+
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestFlushSnap) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ cout << "cluster is not yet octopus, skipping test" << std::endl;
+ return;
+ }
+
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "fingerprint_algorithm", "sha1"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_tier", pool_name),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_chunk_algorithm", "fastcdc"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_cdc_chunk_size", 1024),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create object
+ bufferlist gbl;
+ {
+ //bufferlist bl;
+ //bl.append("there hi");
+ generate_buffer(1024*8, &gbl);
+ ObjectWriteOperation op;
+ op.write_full(gbl);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hi");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, ioctx, cache_ioctx, 2, 2, "bar", "foo");
+ manifest_set_chunk(cluster, ioctx, cache_ioctx, 6, 2, "bar", "foo");
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ // make a dirty chunks
+ {
+ bufferlist bl;
+ bl.append("Thbbe");
+ ASSERT_EQ(0, cache_ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ // and another
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ // make a dirty chunks
+ {
+ bufferlist bl;
+ bl.append("Thcce");
+ ASSERT_EQ(0, cache_ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ // flush on head (should fail)
+ cache_ioctx.snap_set_read(librados::SNAP_HEAD);
+ // flush
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EBUSY, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush on recent snap (should fail)
+ cache_ioctx.snap_set_read(my_snaps[0]);
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EBUSY, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush on oldest snap
+ cache_ioctx.snap_set_read(my_snaps[1]);
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush on oldest snap
+ cache_ioctx.snap_set_read(my_snaps[0]);
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush on oldest snap
+ cache_ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // check chunk's refcount
+ std::unique_ptr<CDC> cdc = CDC::create("fastcdc", cbits(1024)-1);
+ vector<pair<uint64_t, uint64_t>> chunks;
+ bufferlist chunk;
+ cdc->calc_chunks(gbl, &chunks);
+ chunk.substr_of(gbl, chunks[1].first, chunks[1].second);
+ string tgt_oid;
+ {
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1] = {0};
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ SHA1 sha1_gen;
+ int size = chunk.length();
+ sha1_gen.Update((const unsigned char *)chunk.c_str(), size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ tgt_oid = string(p_str);
+ }
+ // read and verify the chunked object
+ {
+ bufferlist test_bl;
+ ASSERT_EQ(2, ioctx.read(tgt_oid, test_bl, 2, 0));
+ ASSERT_EQ(test_bl[1], chunk[1]);
+ }
+
+ cache_ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ASSERT_EQ(4, cache_ioctx.read("foo", bl, 4, 0));
+ ASSERT_EQ('c', bl[2]);
+ }
+
+ cache_ioctx.snap_set_read(my_snaps[0]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(4, cache_ioctx.read("foo", bl, 4, 0));
+ ASSERT_EQ('b', bl[2]);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestFlushDupCount) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ cout << "cluster is not yet octopus, skipping test" << std::endl;
+ return;
+ }
+
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "fingerprint_algorithm", "sha1"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_tier", pool_name),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_chunk_algorithm", "fastcdc"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_cdc_chunk_size", 1024),
+ inbl, NULL, NULL));
+
+ // create object
+ bufferlist gbl;
+ {
+ //bufferlist bl;
+ generate_buffer(1024*8, &gbl);
+ ObjectWriteOperation op;
+ op.write_full(gbl);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // set-chunk to set manifest object
+ {
+ ObjectReadOperation op;
+ op.set_chunk(0, 2, ioctx, "bar", 0,
+ CEPH_OSD_OP_FLAG_WITH_REFERENCE);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ // make a dirty chunks
+ {
+ bufferlist bl;
+ bl.append("Thbbe hi");
+ ASSERT_EQ(0, cache_ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ // and another
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, cache_ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ // make a dirty chunks
+ {
+ bufferlist bl;
+ bl.append("Thcce hi");
+ ASSERT_EQ(0, cache_ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ //flush on oldest snap
+ cache_ioctx.snap_set_read(my_snaps[1]);
+ // flush
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush on oldest snap
+ cache_ioctx.snap_set_read(my_snaps[0]);
+ // flush
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ cache_ioctx.snap_set_read(librados::SNAP_HEAD);
+ // flush
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ std::unique_ptr<CDC> cdc = CDC::create("fastcdc", cbits(1024)-1);
+ vector<pair<uint64_t, uint64_t>> chunks;
+ bufferlist chunk;
+ cdc->calc_chunks(gbl, &chunks);
+ chunk.substr_of(gbl, chunks[1].first, chunks[1].second);
+ string tgt_oid;
+ // check chunk's refcount
+ {
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1] = {0};
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = chunk.length();
+ sha1_gen.Update((const unsigned char *)chunk.c_str(), size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ tgt_oid = string(p_str);
+ ioctx.getxattr(p_str, CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_LE(1u, refs.count());
+ }
+
+ bufferlist chunk2;
+ chunk2.substr_of(gbl, chunks[0].first, chunks[0].second);
+ // check chunk's refcount
+ {
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1] = {0};
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = chunk2.length();
+ sha1_gen.Update((const unsigned char *)chunk2.c_str(), size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ tgt_oid = string(p_str);
+ ioctx.getxattr(p_str, CHUNK_REFCOUNT_ATTR, t);
+ chunk_refs_t refs;
+ try {
+ auto iter = t.cbegin();
+ decode(refs, iter);
+ } catch (buffer::error& err) {
+ ASSERT_TRUE(0);
+ }
+ ASSERT_LE(1u, refs.count());
+ }
+
+ // make a dirty chunks
+ {
+ bufferlist bl;
+ bl.append("ThDDe hi");
+ ASSERT_EQ(0, cache_ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ // flush
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ bufferlist tmp;
+ tmp.append("Thcce hi");
+ gbl.begin(0).copy_in(tmp.length(), tmp);
+ bufferlist chunk3;
+ cdc->calc_chunks(gbl, &chunks);
+ chunk3.substr_of(gbl, chunks[0].first, chunks[0].second);
+ // check chunk's refcount
+ {
+ unsigned char fingerprint[CEPH_CRYPTO_SHA1_DIGESTSIZE + 1] = {0};
+ char p_str[CEPH_CRYPTO_SHA1_DIGESTSIZE*2+1] = {0};
+ bufferlist t;
+ SHA1 sha1_gen;
+ int size = chunk2.length();
+ sha1_gen.Update((const unsigned char *)chunk2.c_str(), size);
+ sha1_gen.Final(fingerprint);
+ buf_to_hex(fingerprint, CEPH_CRYPTO_SHA1_DIGESTSIZE, p_str);
+ is_intended_refcount_state(cache_ioctx, "foo", ioctx, p_str, 0);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, TierFlushDuringFlush) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ cout << "cluster is not yet octopus, skipping test" << std::endl;
+ return;
+ }
+
+ bufferlist inbl;
+
+ // create a new pool
+ std::string temp_pool_name = get_temp_pool_name() + "-test-flush";
+ ASSERT_EQ(0, cluster.pool_create(temp_pool_name.c_str()));
+
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "fingerprint_algorithm", "sha1"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_tier", temp_pool_name),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_chunk_algorithm", "fastcdc"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_cdc_chunk_size", 1024),
+ inbl, NULL, NULL));
+
+ // create object
+ bufferlist gbl;
+ {
+ //bufferlist bl;
+ generate_buffer(1024*8, &gbl);
+ ObjectWriteOperation op;
+ op.write_full(gbl);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // set-chunk to set manifest object
+ {
+ ObjectReadOperation op;
+ op.set_chunk(0, 2, ioctx, "bar", 0,
+ CEPH_OSD_OP_FLAG_WITH_REFERENCE);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // delete temp pool, so flushing chunk will fail
+ ASSERT_EQ(0, s_cluster.pool_delete(temp_pool_name.c_str()));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // flush to check if proper error is returned
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-ENOENT, completion->get_return_value());
+ completion->release();
+ }
+
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestSnapHasChunk) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ cout << "cluster is not yet octopus, skipping test" << std::endl;
+ return;
+ }
+
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(pool_name, "fingerprint_algorithm", "sha1"),
+ inbl, NULL, NULL));
+ cluster.wait_for_latest_osdmap();
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("there HIHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ string er_fp_oid, hi_fp_oid, HI_fp_oid, ai_fp_oid, bi_fp_oid,
+ Er_fp_oid, Hi_fp_oid, SI_fp_oid;
+
+ // get fp_oid
+ er_fp_oid = get_fp_oid("er", "sha1");
+ hi_fp_oid = get_fp_oid("hi", "sha1");
+ HI_fp_oid = get_fp_oid("HI", "sha1");
+ ai_fp_oid = get_fp_oid("ai", "sha1");
+ bi_fp_oid = get_fp_oid("bi", "sha1");
+ Er_fp_oid = get_fp_oid("Er", "sha1");
+ Hi_fp_oid = get_fp_oid("Hi", "sha1");
+ SI_fp_oid = get_fp_oid("SI", "sha1");
+
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("er");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(er_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("hi");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(hi_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("HI");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(HI_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("ai");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(ai_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("bi");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(bi_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("Er");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(Er_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("Hi");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(Hi_fp_oid, &op));
+ }
+ // write
+ {
+ ObjectWriteOperation op;
+ bufferlist bl;
+ bl.append("SI");
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate(SI_fp_oid, &op));
+ }
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, HI_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, HI_fp_oid, "foo");
+
+ // foo head: [hi] [HI]
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+
+ // create a clone
+ {
+ bufferlist bl;
+ bl.append("a");
+ ASSERT_EQ(0, ioctx.write("foo", bl, 1, 6));
+ }
+ // write
+ {
+ bufferlist bl;
+ bl.append("a");
+ ASSERT_EQ(0, ioctx.write("foo", bl, 1, 6));
+ }
+ // write
+ {
+ bufferlist bl;
+ bl.append("S");
+ ASSERT_EQ(0, ioctx.write("foo", bl, 1, 8));
+ }
+
+ // foo snap[0]: [hi] [HI]
+ // foo head : [er] [ai] [SI]
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, er_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, ai_fp_oid, "foo");
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, SI_fp_oid, "foo");
+
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ // create a clone
+ {
+ bufferlist bl;
+ bl.append("b");
+ ASSERT_EQ(0, ioctx.write("foo", bl, 1, 6));
+ }
+ // write
+ {
+ bufferlist bl;
+ bl.append("b");
+ ASSERT_EQ(0, ioctx.write("foo", bl, 1, 6));
+ }
+
+ // foo snap[1]: [HI] [HI]
+ // foo snap[0]: [er] [ai] [SI]
+ // foo head : [er] [bi] [SI]
+
+ // set-chunk (dedup)
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, bi_fp_oid, "foo");
+
+ {
+ ASSERT_EQ(1, cls_cas_references_chunk(ioctx, "foo", SI_fp_oid));
+ ASSERT_EQ(1, cls_cas_references_chunk(ioctx, "foo", er_fp_oid));
+ ASSERT_EQ(1, cls_cas_references_chunk(ioctx, "foo", ai_fp_oid));
+ ASSERT_EQ(2, cls_cas_references_chunk(ioctx, "foo", HI_fp_oid));
+ ASSERT_EQ(-ENOLINK, cls_cas_references_chunk(ioctx, "foo", Hi_fp_oid));
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestRollback) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet pacific
+ if (_get_required_osd_release(cluster) < "pacific") {
+ cout << "cluster is not yet pacific, skipping test" << std::endl;
+ return;
+ }
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("CDere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ABere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk1", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("CDere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk2", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("EFere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk3", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("thABe hiEF");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, "chunk1", "foo");
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, "chunk3", "foo");
+ // foo snap[1]:
+ // foo snap[0]:
+ // foo head : [chunk1] [chunk3]
+
+ ioctx.snap_set_read(my_snaps[1]);
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 0, 10, "chunk2", "foo");
+ // foo snap[1]: [ chunk2 ]
+ // foo snap[0]:
+ // foo head : [chunk1] [chunk3]
+
+ // foo snap[1]: [ chunk2 ]
+ // foo snap[0]:
+ // foo head : [chunk1] [chunk3]
+
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_rollback("foo", my_snaps[0]));
+
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('t', bl[0]);
+ }
+
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_rollback("foo", my_snaps[1]));
+
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('C', bl[0]);
+ }
+
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestRollbackRefcount) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet pacific
+ if (_get_required_osd_release(cluster) < "pacific") {
+ cout << "cluster is not yet pacific, skipping test" << std::endl;
+ return;
+ }
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("CDere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ABere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk1", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("CDere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk2", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("EFere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk3", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("DDDDD hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk4", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("EEEEE hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk5", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("thABe hiEF");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, "chunk1", "foo");
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, "chunk3", "foo");
+ // foo snap[1]:
+ // foo snap[0]:
+ // foo head : [chunk1] [chunk3]
+
+ ioctx.snap_set_read(my_snaps[1]);
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, "chunk4", "foo");
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 6, 2, "chunk5", "foo");
+ // foo snap[1]: [chunk4] [chunk5]
+ // foo snap[0]:
+ // foo head : [chunk1] [chunk3]
+
+ ioctx.snap_set_read(my_snaps[0]);
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 0, 10, "chunk2", "foo");
+ // foo snap[1]: [chunk4] [chunk5]
+ // foo snap[0]: [ chunk2 ]
+ // foo head : [chunk1] [chunk3]
+
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_rollback("foo", my_snaps[1]));
+ // foo snap[1]: [chunk4] [chunk5]
+ // foo snap[0]: [ chunk2 ]
+ // foo head : [chunk4] [chunk5] <-- will contain these contents
+
+ sleep(10);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk1", 0);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk3", 0);
+
+ ioctx.selfmanaged_snap_remove(my_snaps[1]);
+ sleep(10);
+ // foo snap[1]:
+ // foo snap[0]: [ chunk2 ]
+ // foo head : [chunk4] [chunk5]
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk4", 1);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk5", 1);
+
+ {
+ bufferlist bl;
+ bl.append("thABe hiEF");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+ // foo snap[1]:
+ // foo snap[0]: [ chunk2 ]
+ // foo head :
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk1", 0);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk3", 0);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk4", 0);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk5", 0);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk2", 1);
+}
+
+TEST_F(LibRadosTwoPoolsPP, ManifestEvictRollback) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet pacific
+ if (_get_required_osd_release(cluster) < "pacific") {
+ cout << "cluster is not yet pacific, skipping test" << std::endl;
+ return;
+ }
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("CDere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ABere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk1", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("CDere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk2", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("EFere hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("chunk3", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ASSERT_EQ(0, ioctx.write("foo", bl, bl.length(), 0));
+ }
+
+
+ // set-chunk
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 2, 2, "chunk1", "foo");
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 8, 2, "chunk3", "foo");
+ // foo snap[0]:
+ // foo head : [chunk1] [chunk3]
+
+ ioctx.snap_set_read(my_snaps[0]);
+ manifest_set_chunk(cluster, cache_ioctx, ioctx, 0, 10, "chunk2", "foo");
+ // foo snap[0]: [ chunk2 ]
+ // foo head : [chunk1] [chunk3]
+
+ sleep(10);
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk1", 1);
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk3", 1);
+
+
+ ioctx.snap_set_read(my_snaps[0]);
+ // evict--this makes the chunk missing state
+ {
+ ObjectReadOperation op, stat_op;
+ op.tier_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ }
+
+ // rollback to my_snaps[0]
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_rollback("foo", my_snaps[0]));
+
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('C', bl[0]);
+ }
+
+ is_intended_refcount_state(ioctx, "foo", cache_ioctx, "chunk2", 1);
+}
+
+class LibRadosTwoPoolsECPP : public RadosTestECPP
+{
+public:
+ LibRadosTwoPoolsECPP() {};
+ ~LibRadosTwoPoolsECPP() override {};
+protected:
+ static void SetUpTestCase() {
+ SKIP_IF_CRIMSON();
+ pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_ec_pool_pp(pool_name, s_cluster));
+ }
+ static void TearDownTestCase() {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, destroy_one_ec_pool_pp(pool_name, s_cluster));
+ }
+ static std::string cache_pool_name;
+
+ void SetUp() override {
+ SKIP_IF_CRIMSON();
+ cache_pool_name = get_temp_pool_name();
+ ASSERT_EQ(0, s_cluster.pool_create(cache_pool_name.c_str()));
+ RadosTestECPP::SetUp();
+
+ ASSERT_EQ(0, cluster.ioctx_create(cache_pool_name.c_str(), cache_ioctx));
+ cache_ioctx.application_enable("rados", true);
+ cache_ioctx.set_namespace(nspace);
+ }
+ void TearDown() override {
+ SKIP_IF_CRIMSON();
+ // flush + evict cache
+ flush_evict_all(cluster, cache_ioctx);
+
+ bufferlist inbl;
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+
+ RadosTestECPP::TearDown();
+
+ cleanup_default_namespace(cache_ioctx);
+ cleanup_namespace(cache_ioctx, nspace);
+
+ cache_ioctx.close();
+ ASSERT_EQ(0, s_cluster.pool_delete(cache_pool_name.c_str()));
+ }
+
+ librados::IoCtx cache_ioctx;
+};
+
+std::string LibRadosTwoPoolsECPP::cache_pool_name;
+
+TEST_F(LibRadosTierECPP, Dirty) {
+ SKIP_IF_CRIMSON();
+ {
+ ObjectWriteOperation op;
+ op.undirty();
+ ASSERT_EQ(0, ioctx.operate("foo", &op)); // still get 0 if it dne
+ }
+ {
+ ObjectWriteOperation op;
+ op.create(true);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_TRUE(dirty);
+ ASSERT_EQ(0, r);
+ }
+ {
+ ObjectWriteOperation op;
+ op.undirty();
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ ObjectWriteOperation op;
+ op.undirty();
+ ASSERT_EQ(0, ioctx.operate("foo", &op)); // still 0 if already clean
+ }
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ ASSERT_FALSE(dirty);
+ ASSERT_EQ(0, r);
+ }
+ //{
+ // ObjectWriteOperation op;
+ // op.truncate(0); // still a write even tho it is a no-op
+ // ASSERT_EQ(0, ioctx.operate("foo", &op));
+ //}
+ //{
+ // bool dirty = false;
+ // int r = -1;
+ // ObjectReadOperation op;
+ // op.is_dirty(&dirty, &r);
+ // ASSERT_EQ(0, ioctx.operate("foo", &op, NULL));
+ // ASSERT_TRUE(dirty);
+ // ASSERT_EQ(0, r);
+ //}
+}
+
+TEST_F(LibRadosTwoPoolsECPP, Overlay) {
+ SKIP_IF_CRIMSON();
+ // create objects
+ {
+ bufferlist bl;
+ bl.append("base");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("cache");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // by default, the overlay sends us to cache pool
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, cache_ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ // unless we say otherwise
+ {
+ bufferlist bl;
+ ObjectReadOperation op;
+ op.read(0, 1, &bl, NULL);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ ASSERT_EQ('b', bl[0]);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsECPP, Promote) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger a promote
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ }
+
+ // read, trigger a whiteout
+ {
+ bufferlist bl;
+ ASSERT_EQ(-ENOENT, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ(-ENOENT, ioctx.read("bar", bl, 1, 0));
+ }
+
+ // verify the object is present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+}
+
+TEST_F(LibRadosTwoPoolsECPP, PromoteSnap) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ ObjectWriteOperation op;
+ op.remove();
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger a promote on the head
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bam", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ ioctx.snap_set_read(my_snaps[0]);
+
+ // stop and scrub this pg (to make sure scrub can handle missing
+ // clones in the cache tier)
+ // This test requires cache tier and base tier to have the same pg_num/pgp_num
+ {
+ for (int tries = 0; tries < 5; ++tries) {
+ IoCtx cache_ioctx;
+ ASSERT_EQ(0, cluster.ioctx_create(cache_pool_name.c_str(), cache_ioctx));
+ uint32_t hash;
+ ASSERT_EQ(0, ioctx.get_object_pg_hash_position2("foo", &hash));
+ ostringstream ss;
+ ss << "{\"prefix\": \"pg scrub\", \"pgid\": \""
+ << cache_ioctx.get_id() << "."
+ << hash
+ << "\"}";
+ int r = cluster.mon_command(ss.str(), inbl, NULL, NULL);
+ if (r == -EAGAIN ||
+ r == -ENOENT) { // in case mgr osdmap is a bit stale
+ sleep(5);
+ continue;
+ }
+ ASSERT_EQ(0, r);
+ break;
+ }
+ // give it a few seconds to go. this is sloppy but is usually enough time
+ cout << "waiting for scrub..." << std::endl;
+ sleep(15);
+ cout << "done waiting" << std::endl;
+ }
+
+ // read foo snap
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ // read bar snap
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ // read baz snap
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("baz", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+
+ // read foo
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ // read bar
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ // read baz
+ {
+ bufferlist bl;
+ ASSERT_EQ(-ENOENT, ioctx.read("baz", bl, 1, 0));
+ }
+
+ // cleanup
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+}
+
+TEST_F(LibRadosTwoPoolsECPP, PromoteSnapTrimRace) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // delete the snap
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps[0]));
+
+ ioctx.snap_set_read(my_snaps[0]);
+
+ // read foo snap. the OSD may or may not realize that this snap has
+ // been logically deleted; either response is valid.
+ {
+ bufferlist bl;
+ int r = ioctx.read("foo", bl, 1, 0);
+ ASSERT_TRUE(r == 1 || r == -ENOENT);
+ }
+
+ // cleanup
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+}
+
+TEST_F(LibRadosTwoPoolsECPP, Whiteout) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create some whiteouts, verify they behave
+ {
+ ObjectWriteOperation op;
+ op.assert_exists();
+ op.remove();
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ {
+ ObjectWriteOperation op;
+ op.assert_exists();
+ op.remove();
+ ASSERT_EQ(-ENOENT, ioctx.operate("bar", &op));
+ }
+ {
+ ObjectWriteOperation op;
+ op.assert_exists();
+ op.remove();
+ ASSERT_EQ(-ENOENT, ioctx.operate("bar", &op));
+ }
+
+ // verify the whiteouts are there in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // delete a whiteout and verify it goes away
+ ASSERT_EQ(-ENOENT, ioctx.remove("foo"));
+ {
+ ObjectWriteOperation op;
+ op.remove();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // recreate an object and verify we can read it
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+}
+
+TEST_F(LibRadosTwoPoolsECPP, Evict) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger a promote
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ }
+
+ // read, trigger a whiteout, and a dirty object
+ {
+ bufferlist bl;
+ ASSERT_EQ(-ENOENT, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ(-ENOENT, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ(0, ioctx.write("bar", bl, bl.length(), 0));
+ }
+
+ // verify the object is present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it->get_oid() == string("foo") || it->get_oid() == string("bar"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // pin
+ {
+ ObjectWriteOperation op;
+ op.cache_pin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // evict the pinned object with -EPERM
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE,
+ NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EPERM, completion->get_return_value());
+ completion->release();
+ }
+
+ // unpin
+ {
+ ObjectWriteOperation op;
+ op.cache_unpin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify clean
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op, NULL));
+ ASSERT_FALSE(dirty);
+ ASSERT_EQ(0, r);
+ }
+
+ // evict
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE,
+ NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EBUSY, completion->get_return_value());
+ completion->release();
+ }
+}
+
+TEST_F(LibRadosTwoPoolsECPP, EvictSnap) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ ObjectWriteOperation op;
+ op.remove();
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("ciao!");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger a promote on the head
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bam", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+
+ // evict bam
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "bam", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ {
+ bufferlist bl;
+ ObjectReadOperation op;
+ op.read(1, 0, &bl, NULL);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "bam", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-ENOENT, completion->get_return_value());
+ completion->release();
+ }
+
+ // read foo snap
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ // evict foo snap
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // snap is gone...
+ {
+ bufferlist bl;
+ ObjectReadOperation op;
+ op.read(1, 0, &bl, NULL);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-ENOENT, completion->get_return_value());
+ completion->release();
+ }
+ // head is still there...
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ObjectReadOperation op;
+ op.read(1, 0, &bl, NULL);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // promote head + snap of bar
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ // evict bar head (fail)
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EBUSY, completion->get_return_value());
+ completion->release();
+ }
+
+ // evict bar snap
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // ...and then head
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ObjectReadOperation op;
+ op.read(1, 0, &bl, NULL);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "bar", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // cleanup
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+}
+
+TEST_F(LibRadosTwoPoolsECPP, TryFlush) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // verify the object is present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // verify the object is NOT present in the base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+
+ // verify dirty
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op, NULL));
+ ASSERT_TRUE(dirty);
+ ASSERT_EQ(0, r);
+ }
+
+ // pin
+ {
+ ObjectWriteOperation op;
+ op.cache_pin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush the pinned object with -EPERM
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EPERM, completion->get_return_value());
+ completion->release();
+ }
+
+ // unpin
+ {
+ ObjectWriteOperation op;
+ op.cache_unpin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify clean
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op, NULL));
+ ASSERT_FALSE(dirty);
+ ASSERT_EQ(0, r);
+ }
+
+ // verify in base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it != ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+
+ // evict it
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op, librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify no longer in cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+}
+
+TEST_F(LibRadosTwoPoolsECPP, FailedFlush) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // verify the object is present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // verify the object is NOT present in the base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+
+ // set omap
+ {
+ ObjectWriteOperation op;
+ std::map<std::string, bufferlist> omap;
+ omap["somekey"] = bufferlist();
+ op.omap_set(omap);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_NE(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // get omap
+ {
+ ObjectReadOperation op;
+ bufferlist bl;
+ int prval = 0;
+ std::set<std::string> keys;
+ keys.insert("somekey");
+ std::map<std::string, bufferlist> map;
+
+ op.omap_get_vals_by_keys(keys, &map, &prval);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op, &bl));
+ sleep(5);
+ bool completed = completion->is_complete();
+ if( !completed ) {
+ cache_ioctx.aio_cancel(completion);
+ std::cerr << "Most probably test case will hang here, please reset manually" << std::endl;
+ ASSERT_TRUE(completed); //in fact we are locked forever at test case shutdown unless fix for http://tracker.ceph.com/issues/14511 is applied. Seems there is no workaround for that
+ }
+ completion->release();
+ }
+ // verify still not in base tier
+ {
+ ASSERT_TRUE(ioctx.nobjects_begin() == ioctx.nobjects_end());
+ }
+ // erase it
+ {
+ ObjectWriteOperation op;
+ op.remove();
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ // flush whiteout
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // evict
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op, librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify no longer in cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+ // or base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+}
+
+TEST_F(LibRadosTwoPoolsECPP, Flush) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ uint64_t user_version = 0;
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // verify the object is present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // verify the object is NOT present in the base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+
+ // verify dirty
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op, NULL));
+ ASSERT_TRUE(dirty);
+ ASSERT_EQ(0, r);
+ user_version = cache_ioctx.get_last_version();
+ }
+
+ // pin
+ {
+ ObjectWriteOperation op;
+ op.cache_pin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush the pinned object with -EPERM
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EPERM, completion->get_return_value());
+ completion->release();
+ }
+
+ // unpin
+ {
+ ObjectWriteOperation op;
+ op.cache_unpin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify clean
+ {
+ bool dirty = false;
+ int r = -1;
+ ObjectReadOperation op;
+ op.is_dirty(&dirty, &r);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op, NULL));
+ ASSERT_FALSE(dirty);
+ ASSERT_EQ(0, r);
+ }
+
+ // verify in base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it != ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+
+ // evict it
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op, librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify no longer in cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // read it again and verify the version is consistent
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, cache_ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ(user_version, cache_ioctx.get_last_version());
+ }
+
+ // erase it
+ {
+ ObjectWriteOperation op;
+ op.remove();
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // flush whiteout
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // evict
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op, librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify no longer in cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+ // or base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+}
+
+TEST_F(LibRadosTwoPoolsECPP, FlushSnap) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("a");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // create a snapshot, clone
+ vector<uint64_t> my_snaps(1);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("b");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // and another
+ my_snaps.resize(2);
+ my_snaps[1] = my_snaps[0];
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0],
+ my_snaps));
+ {
+ bufferlist bl;
+ bl.append("c");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // verify the object is present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // verify the object is NOT present in the base tier
+ {
+ NObjectIterator it = ioctx.nobjects_begin();
+ ASSERT_TRUE(it == ioctx.nobjects_end());
+ }
+
+ // flush on head (should fail)
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EBUSY, completion->get_return_value());
+ completion->release();
+ }
+ // flush on recent snap (should fail)
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EBUSY, completion->get_return_value());
+ completion->release();
+ }
+ // flush on oldest snap
+ ioctx.snap_set_read(my_snaps[1]);
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // flush on next oldest snap
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // flush on head
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // verify i can read the snaps from the cache pool
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('b', bl[0]);
+ }
+ ioctx.snap_set_read(my_snaps[1]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('a', bl[0]);
+ }
+
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // verify i can read the snaps from the base pool
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('c', bl[0]);
+ }
+ ioctx.snap_set_read(my_snaps[0]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('b', bl[0]);
+ }
+ ioctx.snap_set_read(my_snaps[1]);
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('a', bl[0]);
+ }
+
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ cluster.wait_for_latest_osdmap();
+
+ // cleanup
+ ioctx.selfmanaged_snap_remove(my_snaps[0]);
+}
+
+TEST_F(LibRadosTierECPP, FlushWriteRaces) {
+ SKIP_IF_CRIMSON();
+ Rados cluster;
+ std::string pool_name = get_temp_pool_name();
+ std::string cache_pool_name = pool_name + "-cache";
+ ASSERT_EQ("", create_one_pool_pp(pool_name, cluster));
+ ASSERT_EQ(0, cluster.pool_create(cache_pool_name.c_str()));
+ IoCtx cache_ioctx;
+ ASSERT_EQ(0, cluster.ioctx_create(cache_pool_name.c_str(), cache_ioctx));
+ cache_ioctx.application_enable("rados", true);
+ IoCtx ioctx;
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx));
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create/dirty object
+ bufferlist bl;
+ bl.append("hi there");
+ {
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // flush + write
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+
+ ObjectWriteOperation op2;
+ op2.write_full(bl);
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate(
+ "foo", completion2, &op2, 0));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ }
+
+ int tries = 1000;
+ do {
+ // create/dirty object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // try-flush + write
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ ObjectWriteOperation op2;
+ op2.write_full(bl);
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion2, &op2, 0));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ int r = completion->get_return_value();
+ ASSERT_TRUE(r == -EBUSY || r == 0);
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ if (r == -EBUSY)
+ break;
+ cout << "didn't get EBUSY, trying again" << std::endl;
+ }
+ ASSERT_TRUE(--tries);
+ } while (true);
+
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+
+ ASSERT_EQ(0, cluster.pool_delete(cache_pool_name.c_str()));
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster));
+}
+
+TEST_F(LibRadosTwoPoolsECPP, FlushTryFlushRaces) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create/dirty object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // flush + flush
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+
+ ObjectReadOperation op2;
+ op2.cache_flush();
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion2, &op2,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ }
+
+ // create/dirty object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // flush + try-flush
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+
+ ObjectReadOperation op2;
+ op2.cache_try_flush();
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion2, &op2,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ }
+
+ // create/dirty object
+ int tries = 1000;
+ do {
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // try-flush + flush
+ // (flush will not piggyback on try-flush)
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ ObjectReadOperation op2;
+ op2.cache_flush();
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion2, &op2,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ int r = completion->get_return_value();
+ ASSERT_TRUE(r == -EBUSY || r == 0);
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ if (r == -EBUSY)
+ break;
+ cout << "didn't get EBUSY, trying again" << std::endl;
+ }
+ ASSERT_TRUE(--tries);
+ } while (true);
+
+ // create/dirty object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // try-flush + try-flush
+ {
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ ObjectReadOperation op2;
+ op2.cache_try_flush();
+ librados::AioCompletion *completion2 = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion2, &op2,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ completion->wait_for_complete();
+ completion2->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ ASSERT_EQ(0, completion2->get_return_value());
+ completion->release();
+ completion2->release();
+ }
+}
+
+TEST_F(LibRadosTwoPoolsECPP, TryFlushReadRace) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create/dirty object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ bufferptr bp(4000000); // make it big!
+ bp.zero();
+ bl.append(bp);
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // start a continuous stream of reads
+ read_ioctx = &ioctx;
+ test_lock.lock();
+ for (int i = 0; i < max_reads; ++i) {
+ start_flush_read();
+ num_reads++;
+ }
+ test_lock.unlock();
+
+ // try-flush
+ ObjectReadOperation op;
+ op.cache_try_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY |
+ librados::OPERATION_SKIPRWLOCKS, NULL));
+
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+
+ // stop reads
+ std::unique_lock locker{test_lock};
+ max_reads = 0;
+ cond.wait(locker, [] { return num_reads == 0;});
+}
+
+TEST_F(LibRadosTierECPP, CallForcesPromote) {
+ SKIP_IF_CRIMSON();
+ Rados cluster;
+ std::string pool_name = get_temp_pool_name();
+ std::string cache_pool_name = pool_name + "-cache";
+ ASSERT_EQ("", create_one_ec_pool_pp(pool_name, cluster));
+ ASSERT_EQ(0, cluster.pool_create(cache_pool_name.c_str()));
+ IoCtx cache_ioctx;
+ ASSERT_EQ(0, cluster.ioctx_create(cache_pool_name.c_str(), cache_ioctx));
+ cache_ioctx.application_enable("rados", true);
+ IoCtx ioctx;
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx));
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // set things up such that the op would normally be proxied
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_count", 2),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_period", 600),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_type",
+ "explicit_object"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "min_read_recency_for_promote",
+ "4"),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // create/dirty object
+ bufferlist bl;
+ bl.append("hi there");
+ {
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // flush
+ {
+ ObjectReadOperation op;
+ op.cache_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op,
+ librados::OPERATION_IGNORE_OVERLAY, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // evict
+ {
+ ObjectReadOperation op;
+ op.cache_evict();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE,
+ NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // call
+ {
+ ObjectReadOperation op;
+ bufferlist bl;
+ op.exec("rbd", "get_id", bl);
+ bufferlist out;
+ // should get EIO (not an rbd object), not -EOPNOTSUPP (we didn't promote)
+ ASSERT_EQ(-5, ioctx.operate("foo", &op, &out));
+ }
+
+ // make sure foo is back in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ ASSERT_TRUE(it->get_oid() == string("foo"));
+ ++it;
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+
+ ASSERT_EQ(0, cluster.pool_delete(cache_pool_name.c_str()));
+ ASSERT_EQ(0, destroy_one_ec_pool_pp(pool_name, cluster));
+}
+
+TEST_F(LibRadosTierECPP, HitSetNone) {
+ SKIP_IF_CRIMSON();
+ {
+ list< pair<time_t,time_t> > ls;
+ AioCompletion *c = librados::Rados::aio_create_completion();
+ ASSERT_EQ(0, ioctx.hit_set_list(123, c, &ls));
+ c->wait_for_complete();
+ ASSERT_EQ(0, c->get_return_value());
+ ASSERT_TRUE(ls.empty());
+ c->release();
+ }
+ {
+ bufferlist bl;
+ AioCompletion *c = librados::Rados::aio_create_completion();
+ ASSERT_EQ(0, ioctx.hit_set_get(123, c, 12345, &bl));
+ c->wait_for_complete();
+ ASSERT_EQ(-ENOENT, c->get_return_value());
+ c->release();
+ }
+}
+
+TEST_F(LibRadosTwoPoolsECPP, HitSetRead) {
+ SKIP_IF_CRIMSON();
+ // make it a tier
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+
+ // enable hitset tracking for this pool
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_count", 2),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_period", 600),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_type",
+ "explicit_object"),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ cache_ioctx.set_namespace("");
+
+ // keep reading until we see our object appear in the HitSet
+ utime_t start = ceph_clock_now();
+ utime_t hard_stop = start + utime_t(600, 0);
+
+ while (true) {
+ utime_t now = ceph_clock_now();
+ ASSERT_TRUE(now < hard_stop);
+
+ string name = "foo";
+ uint32_t hash;
+ ASSERT_EQ(0, cache_ioctx.get_object_hash_position2(name, &hash));
+ hobject_t oid(sobject_t(name, CEPH_NOSNAP), "", hash,
+ cluster.pool_lookup(cache_pool_name.c_str()), "");
+
+ bufferlist bl;
+ ASSERT_EQ(-ENOENT, cache_ioctx.read("foo", bl, 1, 0));
+
+ bufferlist hbl;
+ AioCompletion *c = librados::Rados::aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.hit_set_get(hash, c, now.sec(), &hbl));
+ c->wait_for_complete();
+ c->release();
+
+ if (hbl.length()) {
+ auto p = hbl.cbegin();
+ HitSet hs;
+ decode(hs, p);
+ if (hs.contains(oid)) {
+ cout << "ok, hit_set contains " << oid << std::endl;
+ break;
+ }
+ cout << "hmm, not in HitSet yet" << std::endl;
+ } else {
+ cout << "hmm, no HitSet yet" << std::endl;
+ }
+
+ sleep(1);
+ }
+}
+
+// disable this test until hitset-get reliably works on EC pools
+#if 0
+TEST_F(LibRadosTierECPP, HitSetWrite) {
+ int num_pg = _get_pg_num(cluster, pool_name);
+ ceph_assert(num_pg > 0);
+
+ // enable hitset tracking for this pool
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(pool_name, "hit_set_count", 8),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(pool_name, "hit_set_period", 600),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(pool_name, "hit_set_type",
+ "explicit_hash"),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ ioctx.set_namespace("");
+
+ // do a bunch of writes
+ for (int i=0; i<1000; ++i) {
+ bufferlist bl;
+ bl.append("a");
+ ASSERT_EQ(0, ioctx.write(stringify(i), bl, 1, 0));
+ }
+
+ // get HitSets
+ std::map<int,HitSet> hitsets;
+ for (int i=0; i<num_pg; ++i) {
+ list< pair<time_t,time_t> > ls;
+ AioCompletion *c = librados::Rados::aio_create_completion();
+ ASSERT_EQ(0, ioctx.hit_set_list(i, c, &ls));
+ c->wait_for_complete();
+ c->release();
+ std::cout << "pg " << i << " ls " << ls << std::endl;
+ ASSERT_FALSE(ls.empty());
+
+ // get the latest
+ c = librados::Rados::aio_create_completion();
+ bufferlist bl;
+ ASSERT_EQ(0, ioctx.hit_set_get(i, c, ls.back().first, &bl));
+ c->wait_for_complete();
+ c->release();
+
+ //std::cout << "bl len is " << bl.length() << "\n";
+ //bl.hexdump(std::cout);
+ //std::cout << std::endl;
+
+ auto p = bl.cbegin();
+ decode(hitsets[i], p);
+
+ // cope with racing splits by refreshing pg_num
+ if (i == num_pg - 1)
+ num_pg = _get_pg_num(cluster, pool_name);
+ }
+
+ for (int i=0; i<1000; ++i) {
+ string n = stringify(i);
+ uint32_t hash = ioctx.get_object_hash_position(n);
+ hobject_t oid(sobject_t(n, CEPH_NOSNAP), "", hash,
+ cluster.pool_lookup(pool_name.c_str()), "");
+ std::cout << "checking for " << oid << std::endl;
+ bool found = false;
+ for (int p=0; p<num_pg; ++p) {
+ if (hitsets[p].contains(oid)) {
+ found = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(found);
+ }
+}
+#endif
+
+TEST_F(LibRadosTwoPoolsECPP, HitSetTrim) {
+ SKIP_IF_CRIMSON();
+ unsigned count = 3;
+ unsigned period = 3;
+
+ // make it a tier
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+
+ // enable hitset tracking for this pool
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_count", count),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_period", period),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_type", "bloom"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(set_pool_str(cache_pool_name, "hit_set_fpp", ".01"),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ cache_ioctx.set_namespace("");
+
+ // do a bunch of writes and make sure the hitsets rotate
+ utime_t start = ceph_clock_now();
+ utime_t hard_stop = start + utime_t(count * period * 50, 0);
+
+ time_t first = 0;
+ int bsize = alignment;
+ char *buf = (char *)new char[bsize];
+ memset(buf, 'f', bsize);
+
+ while (true) {
+ string name = "foo";
+ uint32_t hash;
+ ASSERT_EQ(0, cache_ioctx.get_object_hash_position2(name, &hash));
+ hobject_t oid(sobject_t(name, CEPH_NOSNAP), "", hash, -1, "");
+
+ bufferlist bl;
+ bl.append(buf, bsize);
+ ASSERT_EQ(0, cache_ioctx.append("foo", bl, bsize));
+
+ list<pair<time_t, time_t> > ls;
+ AioCompletion *c = librados::Rados::aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.hit_set_list(hash, c, &ls));
+ c->wait_for_complete();
+ c->release();
+
+ cout << " got ls " << ls << std::endl;
+ if (!ls.empty()) {
+ if (!first) {
+ first = ls.front().first;
+ cout << "first is " << first << std::endl;
+ } else {
+ if (ls.front().first != first) {
+ cout << "first now " << ls.front().first << ", trimmed" << std::endl;
+ break;
+ }
+ }
+ }
+
+ utime_t now = ceph_clock_now();
+ ASSERT_TRUE(now < hard_stop);
+
+ sleep(1);
+ }
+ delete[] buf;
+}
+
+TEST_F(LibRadosTwoPoolsECPP, PromoteOn2ndRead) {
+ SKIP_IF_CRIMSON();
+ // create object
+ for (int i=0; i<20; ++i) {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo" + stringify(i), &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // enable hitset tracking for this pool
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_count", 2),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_period", 600),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_type", "bloom"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "min_read_recency_for_promote", 1),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_grade_decay_rate", 20),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_search_last_n", 1),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ int fake = 0; // set this to non-zero to test spurious promotion,
+ // e.g. from thrashing
+ int attempt = 0;
+ string obj;
+ while (true) {
+ // 1st read, don't trigger a promote
+ obj = "foo" + stringify(attempt);
+ cout << obj << std::endl;
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read(obj.c_str(), bl, 1, 0));
+ if (--fake >= 0) {
+ sleep(1);
+ ASSERT_EQ(1, ioctx.read(obj.c_str(), bl, 1, 0));
+ sleep(1);
+ }
+ }
+
+ // verify the object is NOT present in the cache tier
+ {
+ bool found = false;
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ while (it != cache_ioctx.nobjects_end()) {
+ cout << " see " << it->get_oid() << std::endl;
+ if (it->get_oid() == string(obj.c_str())) {
+ found = true;
+ break;
+ }
+ ++it;
+ }
+ if (!found)
+ break;
+ }
+
+ ++attempt;
+ ASSERT_LE(attempt, 20);
+ cout << "hrm, object is present in cache on attempt " << attempt
+ << ", retrying" << std::endl;
+ }
+
+ // Read until the object is present in the cache tier
+ cout << "verifying " << obj << " is eventually promoted" << std::endl;
+ while (true) {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read(obj.c_str(), bl, 1, 0));
+
+ bool there = false;
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ while (it != cache_ioctx.nobjects_end()) {
+ if (it->get_oid() == string(obj.c_str())) {
+ there = true;
+ break;
+ }
+ ++it;
+ }
+ if (there)
+ break;
+
+ sleep(1);
+ }
+
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsECPP, ProxyRead) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"readproxy\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read and verify the object
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('h', bl[0]);
+ }
+
+ // Verify 10 times the object is NOT present in the cache tier
+ uint32_t i = 0;
+ while (i++ < 10) {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ sleep(1);
+ }
+
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsECPP, CachePin) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("baz", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bam", &op));
+ }
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // read, trigger promote
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ(1, ioctx.read("bar", bl, 1, 0));
+ ASSERT_EQ(1, ioctx.read("baz", bl, 1, 0));
+ ASSERT_EQ(1, ioctx.read("bam", bl, 1, 0));
+ }
+
+ // verify the objects are present in the cache tier
+ {
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ ASSERT_TRUE(it != cache_ioctx.nobjects_end());
+ for (uint32_t i = 0; i < 4; i++) {
+ ASSERT_TRUE(it->get_oid() == string("foo") ||
+ it->get_oid() == string("bar") ||
+ it->get_oid() == string("baz") ||
+ it->get_oid() == string("bam"));
+ ++it;
+ }
+ ASSERT_TRUE(it == cache_ioctx.nobjects_end());
+ }
+
+ // pin objects
+ {
+ ObjectWriteOperation op;
+ op.cache_pin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ {
+ ObjectWriteOperation op;
+ op.cache_pin();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("baz", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // enable agent
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_count", 2),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_period", 600),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_type", "bloom"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "min_read_recency_for_promote", 1),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "target_max_objects", 1),
+ inbl, NULL, NULL));
+
+ sleep(10);
+
+ // Verify the pinned object 'foo' is not flushed/evicted
+ uint32_t count = 0;
+ while (true) {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("baz", bl, 1, 0));
+
+ count = 0;
+ NObjectIterator it = cache_ioctx.nobjects_begin();
+ while (it != cache_ioctx.nobjects_end()) {
+ ASSERT_TRUE(it->get_oid() == string("foo") ||
+ it->get_oid() == string("bar") ||
+ it->get_oid() == string("baz") ||
+ it->get_oid() == string("bam"));
+ ++count;
+ ++it;
+ }
+ if (count == 2) {
+ ASSERT_TRUE(it->get_oid() == string("foo") ||
+ it->get_oid() == string("baz"));
+ break;
+ }
+
+ sleep(1);
+ }
+
+ // tear down tiers
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + pool_name +
+ "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+TEST_F(LibRadosTwoPoolsECPP, SetRedirectRead) {
+ SKIP_IF_CRIMSON();
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hi there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("bar", &op));
+ }
+
+ // configure tier
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ {
+ ObjectWriteOperation op;
+ op.set_redirect("bar", cache_ioctx, 0);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // read and verify the object
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('t', bl[0]);
+ }
+
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier remove\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsECPP, SetChunkRead) {
+ SKIP_IF_CRIMSON();
+ // note: require >= mimic
+
+ {
+ bufferlist bl;
+ bl.append("there hi");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op));
+ }
+
+ {
+ bufferlist bl;
+ bl.append("There hi");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // set_chunk
+ manifest_set_chunk(cluster, ioctx, cache_ioctx, 0, 4, "bar", "foo");
+
+ // promote
+ {
+ ObjectWriteOperation op;
+ op.tier_promote();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // read and verify the object
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, cache_ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('T', bl[0]);
+ }
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsECPP, ManifestPromoteRead) {
+ SKIP_IF_CRIMSON();
+ // note: require >= mimic
+
+ // create object
+ {
+ bufferlist bl;
+ bl.append("hiaa there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("base chunk");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, cache_ioctx.operate("foo-chunk", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("HIaa there");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("BASE CHUNK");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar-chunk", &op));
+ }
+
+ // set-redirect
+ {
+ ObjectWriteOperation op;
+ op.set_redirect("bar", ioctx, 0);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // set-chunk
+ manifest_set_chunk(cluster, ioctx, cache_ioctx, 0, 10, "bar-chunk", "foo-chunk");
+ // promote
+ {
+ ObjectWriteOperation op;
+ op.tier_promote();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // read and verify the object (redirect)
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, cache_ioctx.read("foo", bl, 1, 0));
+ ASSERT_EQ('H', bl[0]);
+ }
+ // promote
+ {
+ ObjectWriteOperation op;
+ op.tier_promote();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo-chunk", completion, &op));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+ // read and verify the object
+ {
+ bufferlist bl;
+ ASSERT_EQ(1, cache_ioctx.read("foo-chunk", bl, 1, 0));
+ ASSERT_EQ('B', bl[0]);
+ }
+
+ // wait for maps to settle before next test
+ cluster.wait_for_latest_osdmap();
+}
+
+TEST_F(LibRadosTwoPoolsECPP, TrySetDedupTier) {
+ SKIP_IF_CRIMSON();
+ // note: require >= mimic
+
+ bufferlist inbl;
+ ASSERT_EQ(-EOPNOTSUPP, cluster.mon_command(
+ set_pool_str(pool_name, "dedup_tier", cache_pool_name),
+ inbl, NULL, NULL));
+}
+
+TEST_F(LibRadosTwoPoolsPP, PropagateBaseTierError) {
+ SKIP_IF_CRIMSON();
+ // write object to base tier
+ bufferlist omap_bl;
+ encode(static_cast<uint32_t>(0U), omap_bl);
+
+ ObjectWriteOperation op1;
+ op1.omap_set({{"somekey", omap_bl}});
+ ASSERT_EQ(0, ioctx.operate("propagate-base-tier-error", &op1));
+
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_type", "bloom"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_count", 1),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_period", 600),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "target_max_objects", 250),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // guarded op should fail so expect error to propagate to cache tier
+ bufferlist test_omap_bl;
+ encode(static_cast<uint32_t>(1U), test_omap_bl);
+
+ ObjectWriteOperation op2;
+ op2.omap_cmp({{"somekey", {test_omap_bl, CEPH_OSD_CMPXATTR_OP_EQ}}}, nullptr);
+ op2.omap_set({{"somekey", test_omap_bl}});
+
+ ASSERT_EQ(-ECANCELED, ioctx.operate("propagate-base-tier-error", &op2));
+}
+
+TEST_F(LibRadosTwoPoolsPP, HelloWriteReturn) {
+ SKIP_IF_CRIMSON();
+ // configure cache
+ bufferlist inbl;
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier add\", \"pool\": \"" + pool_name +
+ "\", \"tierpool\": \"" + cache_pool_name +
+ "\", \"force_nonempty\": \"--force-nonempty\" }",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + pool_name +
+ "\", \"overlaypool\": \"" + cache_pool_name + "\"}",
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + cache_pool_name +
+ "\", \"mode\": \"writeback\"}",
+ inbl, NULL, NULL));
+
+ // set things up such that the op would normally be proxied
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_count", 2),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_period", 600),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "hit_set_type",
+ "explicit_object"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "min_read_recency_for_promote",
+ "10000"),
+ inbl, NULL, NULL));
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // this *will* return data due to the RETURNVEC flag
+ {
+ bufferlist in, out;
+ int rval;
+ ObjectWriteOperation o;
+ o.exec("hello", "write_return_data", in, &out, &rval);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &o,
+ librados::OPERATION_RETURNVEC));
+ completion->wait_for_complete();
+ ASSERT_EQ(42, completion->get_return_value());
+ ASSERT_EQ(42, rval);
+ out.hexdump(std::cout);
+ ASSERT_EQ("you might see this", std::string(out.c_str(), out.length()));
+ }
+
+ // this will overflow because the return data is too big
+ {
+ bufferlist in, out;
+ int rval;
+ ObjectWriteOperation o;
+ o.exec("hello", "write_too_much_return_data", in, &out, &rval);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_operate("foo", completion, &o,
+ librados::OPERATION_RETURNVEC));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EOVERFLOW, completion->get_return_value());
+ ASSERT_EQ(-EOVERFLOW, rval);
+ ASSERT_EQ("", std::string(out.c_str(), out.length()));
+ }
+}
+
+TEST_F(LibRadosTwoPoolsPP, TierFlushDuringUnsetDedupTier) {
+ SKIP_IF_CRIMSON();
+ // skip test if not yet octopus
+ if (_get_required_osd_release(cluster) < "octopus") {
+ cout << "cluster is not yet octopus, skipping test" << std::endl;
+ return;
+ }
+
+ bufferlist inbl;
+
+ // set dedup parameters without dedup_tier
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "fingerprint_algorithm", "sha1"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_chunk_algorithm", "fastcdc"),
+ inbl, NULL, NULL));
+ ASSERT_EQ(0, cluster.mon_command(
+ set_pool_str(cache_pool_name, "dedup_cdc_chunk_size", 1024),
+ inbl, NULL, NULL));
+
+ // create object
+ bufferlist gbl;
+ {
+ generate_buffer(1024*8, &gbl);
+ ObjectWriteOperation op;
+ op.write_full(gbl);
+ ASSERT_EQ(0, cache_ioctx.operate("foo", &op));
+ }
+ {
+ bufferlist bl;
+ bl.append("there hiHI");
+ ObjectWriteOperation op;
+ op.write_full(bl);
+ ASSERT_EQ(0, ioctx.operate("bar", &op));
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // set-chunk to set manifest object
+ {
+ ObjectReadOperation op;
+ op.set_chunk(0, 2, ioctx, "bar", 0, CEPH_OSD_OP_FLAG_WITH_REFERENCE);
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate("foo", completion, &op,
+ librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(0, completion->get_return_value());
+ completion->release();
+ }
+
+ // flush to check if proper error is returned
+ {
+ ObjectReadOperation op;
+ op.tier_flush();
+ librados::AioCompletion *completion = cluster.aio_create_completion();
+ ASSERT_EQ(0, cache_ioctx.aio_operate(
+ "foo", completion, &op, librados::OPERATION_IGNORE_CACHE, NULL));
+ completion->wait_for_complete();
+ ASSERT_EQ(-EINVAL, completion->get_return_value());
+ completion->release();
+ }
+}
+
diff --git a/src/test/librados/watch_notify.cc b/src/test/librados/watch_notify.cc
new file mode 100644
index 000000000..4d880f2c2
--- /dev/null
+++ b/src/test/librados/watch_notify.cc
@@ -0,0 +1,657 @@
+#include "include/rados/librados.h"
+#include "include/rados/rados_types.h"
+#include "test/librados/test.h"
+#include "test/librados/TestCase.h"
+#include "crimson_utils.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <semaphore.h>
+#include "gtest/gtest.h"
+#include "include/encoding.h"
+#include <set>
+#include <map>
+
+typedef RadosTestEC LibRadosWatchNotifyEC;
+
+int notify_sleep = 0;
+
+// notify
+static sem_t sem;
+
+static void watch_notify_test_cb(uint8_t opcode, uint64_t ver, void *arg)
+{
+ std::cout << __func__ << std::endl;
+ sem_post(&sem);
+}
+
+class LibRadosWatchNotify : public RadosTest
+{
+protected:
+ // notify 2
+ bufferlist notify_bl;
+ std::set<uint64_t> notify_cookies;
+ rados_ioctx_t notify_io;
+ const char *notify_oid = nullptr;
+ int notify_err = 0;
+ rados_completion_t notify_comp;
+
+ static void watch_notify2_test_cb(void *arg,
+ uint64_t notify_id,
+ uint64_t cookie,
+ uint64_t notifier_gid,
+ void *data,
+ size_t data_len);
+ static void watch_notify2_test_errcb(void *arg, uint64_t cookie, int err);
+ static void watch_notify2_test_errcb_reconnect(void *arg, uint64_t cookie, int err);
+ static void watch_notify2_test_errcb_aio_reconnect(void *arg, uint64_t cookie, int err);
+};
+
+
+void LibRadosWatchNotify::watch_notify2_test_cb(void *arg,
+ uint64_t notify_id,
+ uint64_t cookie,
+ uint64_t notifier_gid,
+ void *data,
+ size_t data_len)
+{
+ std::cout << __func__ << " from " << notifier_gid << " notify_id " << notify_id
+ << " cookie " << cookie << std::endl;
+ ceph_assert(notifier_gid > 0);
+ auto thiz = reinterpret_cast<LibRadosWatchNotify*>(arg);
+ ceph_assert(thiz);
+ thiz->notify_cookies.insert(cookie);
+ thiz->notify_bl.clear();
+ thiz->notify_bl.append((char*)data, data_len);
+ if (notify_sleep)
+ sleep(notify_sleep);
+ thiz->notify_err = 0;
+ rados_notify_ack(thiz->notify_io, thiz->notify_oid, notify_id, cookie,
+ "reply", 5);
+}
+
+void LibRadosWatchNotify::watch_notify2_test_errcb(void *arg,
+ uint64_t cookie,
+ int err)
+{
+ std::cout << __func__ << " cookie " << cookie << " err " << err << std::endl;
+ ceph_assert(cookie > 1000);
+ auto thiz = reinterpret_cast<LibRadosWatchNotify*>(arg);
+ ceph_assert(thiz);
+ thiz->notify_err = err;
+}
+
+void LibRadosWatchNotify::watch_notify2_test_errcb_reconnect(void *arg,
+ uint64_t cookie,
+ int err)
+{
+ std::cout << __func__ << " cookie " << cookie << " err " << err << std::endl;
+ ceph_assert(cookie > 1000);
+ auto thiz = reinterpret_cast<LibRadosWatchNotify*>(arg);
+ ceph_assert(thiz);
+ thiz->notify_err = rados_unwatch2(thiz->ioctx, cookie);
+ thiz->notify_cookies.erase(cookie); //delete old cookie
+ thiz->notify_err = rados_watch2(thiz->ioctx, thiz->notify_oid, &cookie,
+ watch_notify2_test_cb, watch_notify2_test_errcb_reconnect, thiz);
+ if (thiz->notify_err < 0) {
+ std::cout << __func__ << " reconnect watch failed with error " << thiz->notify_err << std::endl;
+ return;
+ }
+ return;
+}
+
+
+void LibRadosWatchNotify::watch_notify2_test_errcb_aio_reconnect(void *arg,
+ uint64_t cookie,
+ int err)
+{
+ std::cout << __func__ << " cookie " << cookie << " err " << err << std::endl;
+ ceph_assert(cookie > 1000);
+ auto thiz = reinterpret_cast<LibRadosWatchNotify*>(arg);
+ ceph_assert(thiz);
+ thiz->notify_err = rados_aio_unwatch(thiz->ioctx, cookie, thiz->notify_comp);
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &thiz->notify_comp));
+ thiz->notify_cookies.erase(cookie); //delete old cookie
+ thiz->notify_err = rados_aio_watch(thiz->ioctx, thiz->notify_oid, thiz->notify_comp, &cookie,
+ watch_notify2_test_cb, watch_notify2_test_errcb_aio_reconnect, thiz);
+ ASSERT_EQ(0, rados_aio_wait_for_complete(thiz->notify_comp));
+ ASSERT_EQ(0, rados_aio_get_return_value(thiz->notify_comp));
+ rados_aio_release(thiz->notify_comp);
+ if (thiz->notify_err < 0) {
+ std::cout << __func__ << " reconnect watch failed with error " << thiz->notify_err << std::endl;
+ return;
+ }
+ return;
+}
+
+class WatchNotifyTestCtx2;
+
+// --
+
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+TEST_F(LibRadosWatchNotify, WatchNotify) {
+ ASSERT_EQ(0, sem_init(&sem, 0, 0));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ uint64_t handle;
+ ASSERT_EQ(0,
+ rados_watch(ioctx, "foo", 0, &handle, watch_notify_test_cb, NULL));
+ for (unsigned i=0; i<10; ++i) {
+ int r = rados_notify(ioctx, "foo", 0, NULL, 0);
+ if (r == 0) {
+ break;
+ }
+ if (!getenv("ALLOW_TIMEOUTS")) {
+ ASSERT_EQ(0, r);
+ }
+ }
+ TestAlarm alarm;
+ sem_wait(&sem);
+ rados_unwatch(ioctx, "foo", handle);
+
+ // when dne ...
+ ASSERT_EQ(-ENOENT,
+ rados_watch(ioctx, "dne", 0, &handle, watch_notify_test_cb, NULL));
+
+ sem_destroy(&sem);
+}
+
+TEST_F(LibRadosWatchNotifyEC, WatchNotify) {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, sem_init(&sem, 0, 0));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0));
+ uint64_t handle;
+ ASSERT_EQ(0,
+ rados_watch(ioctx, "foo", 0, &handle, watch_notify_test_cb, NULL));
+ for (unsigned i=0; i<10; ++i) {
+ int r = rados_notify(ioctx, "foo", 0, NULL, 0);
+ if (r == 0) {
+ break;
+ }
+ if (!getenv("ALLOW_TIMEOUTS")) {
+ ASSERT_EQ(0, r);
+ }
+ }
+ TestAlarm alarm;
+ sem_wait(&sem);
+ rados_unwatch(ioctx, "foo", handle);
+ sem_destroy(&sem);
+}
+
+#pragma GCC diagnostic pop
+#pragma GCC diagnostic warning "-Wpragmas"
+
+
+// --
+
+TEST_F(LibRadosWatchNotify, Watch2Delete) {
+ notify_io = ioctx;
+ notify_oid = "foo";
+ notify_err = 0;
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, notify_oid, buf, sizeof(buf), 0));
+ uint64_t handle;
+ ASSERT_EQ(0,
+ rados_watch2(ioctx, notify_oid, &handle,
+ watch_notify2_test_cb,
+ watch_notify2_test_errcb, this));
+ ASSERT_EQ(0, rados_remove(ioctx, notify_oid));
+ int left = 300;
+ std::cout << "waiting up to " << left << " for disconnect notification ..."
+ << std::endl;
+ while (notify_err == 0 && --left) {
+ sleep(1);
+ }
+ ASSERT_TRUE(left > 0);
+ ASSERT_EQ(-ENOTCONN, notify_err);
+ int rados_watch_check_err = rados_watch_check(ioctx, handle);
+ // We may hit ENOENT due to socket failure and a forced reconnect
+ EXPECT_TRUE(rados_watch_check_err == -ENOTCONN || rados_watch_check_err == -ENOENT)
+ << "Where rados_watch_check_err = " << rados_watch_check_err;
+ rados_unwatch2(ioctx, handle);
+ rados_watch_flush(cluster);
+}
+
+TEST_F(LibRadosWatchNotify, AioWatchDelete) {
+ notify_io = ioctx;
+ notify_oid = "foo";
+ notify_err = 0;
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, notify_oid, buf, sizeof(buf), 0));
+
+
+ rados_completion_t comp;
+ uint64_t handle;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &comp));
+ rados_aio_watch(ioctx, notify_oid, comp, &handle,
+ watch_notify2_test_cb, watch_notify2_test_errcb, this);
+ ASSERT_EQ(0, rados_aio_wait_for_complete(comp));
+ ASSERT_EQ(0, rados_aio_get_return_value(comp));
+ rados_aio_release(comp);
+ ASSERT_EQ(0, rados_remove(ioctx, notify_oid));
+ int left = 300;
+ std::cout << "waiting up to " << left << " for disconnect notification ..."
+ << std::endl;
+ while (notify_err == 0 && --left) {
+ sleep(1);
+ }
+ ASSERT_TRUE(left > 0);
+ ASSERT_EQ(-ENOTCONN, notify_err);
+ int rados_watch_check_err = rados_watch_check(ioctx, handle);
+ // We may hit ENOENT due to socket failure injection and a forced reconnect
+ EXPECT_TRUE(rados_watch_check_err == -ENOTCONN || rados_watch_check_err == -ENOENT)
+ << "Where rados_watch_check_err = " << rados_watch_check_err;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &comp));
+ rados_aio_unwatch(ioctx, handle, comp);
+ ASSERT_EQ(0, rados_aio_wait_for_complete(comp));
+ ASSERT_EQ(-ENOENT, rados_aio_get_return_value(comp));
+ rados_aio_release(comp);
+}
+
+// --
+
+TEST_F(LibRadosWatchNotify, WatchNotify2) {
+ notify_io = ioctx;
+ notify_oid = "foo";
+ notify_cookies.clear();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, notify_oid, buf, sizeof(buf), 0));
+ uint64_t handle;
+ ASSERT_EQ(0,
+ rados_watch2(ioctx, notify_oid, &handle,
+ watch_notify2_test_cb,
+ watch_notify2_test_errcb_reconnect, this));
+ ASSERT_GT(rados_watch_check(ioctx, handle), 0);
+ char *reply_buf = 0;
+ size_t reply_buf_len;
+ ASSERT_EQ(0, rados_notify2(ioctx, notify_oid,
+ "notify", 6, 300000,
+ &reply_buf, &reply_buf_len));
+ bufferlist reply;
+ reply.append(reply_buf, reply_buf_len);
+ std::map<std::pair<uint64_t,uint64_t>, bufferlist> reply_map;
+ std::set<std::pair<uint64_t,uint64_t> > missed_map;
+ auto reply_p = reply.cbegin();
+ decode(reply_map, reply_p);
+ decode(missed_map, reply_p);
+ ASSERT_EQ(1u, reply_map.size());
+ ASSERT_EQ(0u, missed_map.size());
+ ASSERT_EQ(1u, notify_cookies.size());
+ handle = *notify_cookies.begin();
+ ASSERT_EQ(1u, notify_cookies.count(handle));
+ ASSERT_EQ(5u, reply_map.begin()->second.length());
+ ASSERT_EQ(0, strncmp("reply", reply_map.begin()->second.c_str(), 5));
+ ASSERT_GT(rados_watch_check(ioctx, handle), 0);
+ rados_buffer_free(reply_buf);
+
+ // try it on a non-existent object ... our buffer pointers
+ // should get zeroed.
+ ASSERT_EQ(-ENOENT, rados_notify2(ioctx, "doesnotexist",
+ "notify", 6, 300000,
+ &reply_buf, &reply_buf_len));
+ ASSERT_EQ((char*)0, reply_buf);
+ ASSERT_EQ(0u, reply_buf_len);
+
+ rados_unwatch2(ioctx, handle);
+ rados_watch_flush(cluster);
+}
+
+TEST_F(LibRadosWatchNotify, AioWatchNotify2) {
+ notify_io = ioctx;
+ notify_oid = "foo";
+ notify_cookies.clear();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, notify_oid, buf, sizeof(buf), 0));
+ uint64_t handle;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &notify_comp));
+ rados_aio_watch(ioctx, notify_oid, notify_comp, &handle,
+ watch_notify2_test_cb, watch_notify2_test_errcb_aio_reconnect, this);
+
+ ASSERT_EQ(0, rados_aio_wait_for_complete(notify_comp));
+ ASSERT_EQ(0, rados_aio_get_return_value(notify_comp));
+ rados_aio_release(notify_comp);
+
+ ASSERT_GT(rados_watch_check(ioctx, handle), 0);
+ char *reply_buf = 0;
+ size_t reply_buf_len;
+ ASSERT_EQ(0, rados_notify2(ioctx, notify_oid,
+ "notify", 6, 300000,
+ &reply_buf, &reply_buf_len));
+ bufferlist reply;
+ reply.append(reply_buf, reply_buf_len);
+ std::map<std::pair<uint64_t,uint64_t>, bufferlist> reply_map;
+ std::set<std::pair<uint64_t,uint64_t> > missed_map;
+ auto reply_p = reply.cbegin();
+ decode(reply_map, reply_p);
+ decode(missed_map, reply_p);
+ ASSERT_EQ(1u, reply_map.size());
+ ASSERT_EQ(0u, missed_map.size());
+ ASSERT_EQ(1u, notify_cookies.size());
+ handle = *notify_cookies.begin();
+ ASSERT_EQ(1u, notify_cookies.count(handle));
+ ASSERT_EQ(5u, reply_map.begin()->second.length());
+ ASSERT_EQ(0, strncmp("reply", reply_map.begin()->second.c_str(), 5));
+ ASSERT_GT(rados_watch_check(ioctx, handle), 0);
+ rados_buffer_free(reply_buf);
+
+ // try it on a non-existent object ... our buffer pointers
+ // should get zeroed.
+ ASSERT_EQ(-ENOENT, rados_notify2(ioctx, "doesnotexist",
+ "notify", 6, 300000,
+ &reply_buf, &reply_buf_len));
+ ASSERT_EQ((char*)0, reply_buf);
+ ASSERT_EQ(0u, reply_buf_len);
+
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &notify_comp));
+ rados_aio_unwatch(ioctx, handle, notify_comp);
+ ASSERT_EQ(0, rados_aio_wait_for_complete(notify_comp));
+ ASSERT_EQ(0, rados_aio_get_return_value(notify_comp));
+ rados_aio_release(notify_comp);
+}
+
+TEST_F(LibRadosWatchNotify, AioNotify) {
+ notify_io = ioctx;
+ notify_oid = "foo";
+ notify_cookies.clear();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, notify_oid, buf, sizeof(buf), 0));
+ uint64_t handle;
+ ASSERT_EQ(0,
+ rados_watch2(ioctx, notify_oid, &handle,
+ watch_notify2_test_cb,
+ watch_notify2_test_errcb, this));
+ ASSERT_GT(rados_watch_check(ioctx, handle), 0);
+ char *reply_buf = 0;
+ size_t reply_buf_len;
+ rados_completion_t comp;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &comp));
+ ASSERT_EQ(0, rados_aio_notify(ioctx, "foo", comp, "notify", 6, 300000,
+ &reply_buf, &reply_buf_len));
+ ASSERT_EQ(0, rados_aio_wait_for_complete(comp));
+ ASSERT_EQ(0, rados_aio_get_return_value(comp));
+ rados_aio_release(comp);
+
+ size_t nr_acks, nr_timeouts;
+ notify_ack_t *acks = nullptr;
+ notify_timeout_t *timeouts = nullptr;
+ ASSERT_EQ(0, rados_decode_notify_response(reply_buf, reply_buf_len,
+ &acks, &nr_acks, &timeouts, &nr_timeouts));
+ ASSERT_EQ(1u, nr_acks);
+ ASSERT_EQ(0u, nr_timeouts);
+ ASSERT_EQ(1u, notify_cookies.size());
+ ASSERT_EQ(1u, notify_cookies.count(handle));
+ ASSERT_EQ(5u, acks[0].payload_len);
+ ASSERT_EQ(0, strncmp("reply", acks[0].payload, acks[0].payload_len));
+ ASSERT_GT(rados_watch_check(ioctx, handle), 0);
+ rados_free_notify_response(acks, nr_acks, timeouts);
+ rados_buffer_free(reply_buf);
+
+ // try it on a non-existent object ... our buffer pointers
+ // should get zeroed.
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &comp));
+ ASSERT_EQ(0, rados_aio_notify(ioctx, "doesnotexist", comp, "notify", 6,
+ 300000, &reply_buf, &reply_buf_len));
+ ASSERT_EQ(0, rados_aio_wait_for_complete(comp));
+ ASSERT_EQ(-ENOENT, rados_aio_get_return_value(comp));
+ rados_aio_release(comp);
+ ASSERT_EQ((char*)0, reply_buf);
+ ASSERT_EQ(0u, reply_buf_len);
+
+ rados_unwatch2(ioctx, handle);
+ rados_watch_flush(cluster);
+}
+
+// --
+
+TEST_F(LibRadosWatchNotify, WatchNotify2Multi) {
+ notify_io = ioctx;
+ notify_oid = "foo";
+ notify_cookies.clear();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, notify_oid, buf, sizeof(buf), 0));
+ uint64_t handle1, handle2;
+ ASSERT_EQ(0,
+ rados_watch2(ioctx, notify_oid, &handle1,
+ watch_notify2_test_cb,
+ watch_notify2_test_errcb, this));
+ ASSERT_EQ(0,
+ rados_watch2(ioctx, notify_oid, &handle2,
+ watch_notify2_test_cb,
+ watch_notify2_test_errcb, this));
+ ASSERT_GT(rados_watch_check(ioctx, handle1), 0);
+ ASSERT_GT(rados_watch_check(ioctx, handle2), 0);
+ ASSERT_NE(handle1, handle2);
+ char *reply_buf = 0;
+ size_t reply_buf_len;
+ ASSERT_EQ(0, rados_notify2(ioctx, notify_oid,
+ "notify", 6, 300000,
+ &reply_buf, &reply_buf_len));
+ bufferlist reply;
+ reply.append(reply_buf, reply_buf_len);
+ std::map<std::pair<uint64_t,uint64_t>, bufferlist> reply_map;
+ std::set<std::pair<uint64_t,uint64_t> > missed_map;
+ auto reply_p = reply.cbegin();
+ decode(reply_map, reply_p);
+ decode(missed_map, reply_p);
+ ASSERT_EQ(2u, reply_map.size());
+ ASSERT_EQ(5u, reply_map.begin()->second.length());
+ ASSERT_EQ(0u, missed_map.size());
+ ASSERT_EQ(2u, notify_cookies.size());
+ ASSERT_EQ(1u, notify_cookies.count(handle1));
+ ASSERT_EQ(1u, notify_cookies.count(handle2));
+ ASSERT_EQ(0, strncmp("reply", reply_map.begin()->second.c_str(), 5));
+ ASSERT_GT(rados_watch_check(ioctx, handle1), 0);
+ ASSERT_GT(rados_watch_check(ioctx, handle2), 0);
+ rados_buffer_free(reply_buf);
+ rados_unwatch2(ioctx, handle1);
+ rados_unwatch2(ioctx, handle2);
+ rados_watch_flush(cluster);
+}
+
+// --
+
+TEST_F(LibRadosWatchNotify, WatchNotify2Timeout) {
+ notify_io = ioctx;
+ notify_oid = "foo";
+ notify_sleep = 3; // 3s
+ notify_cookies.clear();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, notify_oid, buf, sizeof(buf), 0));
+ uint64_t handle;
+ ASSERT_EQ(0,
+ rados_watch2(ioctx, notify_oid, &handle,
+ watch_notify2_test_cb,
+ watch_notify2_test_errcb, this));
+ ASSERT_GT(rados_watch_check(ioctx, handle), 0);
+ char *reply_buf = 0;
+ size_t reply_buf_len;
+ ASSERT_EQ(-ETIMEDOUT, rados_notify2(ioctx, notify_oid,
+ "notify", 6, 1000, // 1s
+ &reply_buf, &reply_buf_len));
+ ASSERT_EQ(1u, notify_cookies.size());
+ {
+ bufferlist reply;
+ reply.append(reply_buf, reply_buf_len);
+ std::map<std::pair<uint64_t,uint64_t>, bufferlist> reply_map;
+ std::set<std::pair<uint64_t,uint64_t> > missed_map;
+ auto reply_p = reply.cbegin();
+ decode(reply_map, reply_p);
+ decode(missed_map, reply_p);
+ ASSERT_EQ(0u, reply_map.size());
+ ASSERT_EQ(1u, missed_map.size());
+ }
+ rados_buffer_free(reply_buf);
+
+ // we should get the next notify, though!
+ notify_sleep = 0;
+ notify_cookies.clear();
+ ASSERT_EQ(0, rados_notify2(ioctx, notify_oid,
+ "notify", 6, 300000, // 300s
+ &reply_buf, &reply_buf_len));
+ ASSERT_EQ(1u, notify_cookies.size());
+ ASSERT_GT(rados_watch_check(ioctx, handle), 0);
+
+ rados_unwatch2(ioctx, handle);
+
+ rados_completion_t comp;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &comp));
+ rados_aio_watch_flush(cluster, comp);
+ ASSERT_EQ(0, rados_aio_wait_for_complete(comp));
+ ASSERT_EQ(0, rados_aio_get_return_value(comp));
+ rados_aio_release(comp);
+ rados_buffer_free(reply_buf);
+
+}
+
+TEST_F(LibRadosWatchNotify, Watch3Timeout) {
+ notify_io = ioctx;
+ notify_oid = "foo";
+ notify_cookies.clear();
+ notify_err = 0;
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, notify_oid, buf, sizeof(buf), 0));
+ uint64_t handle;
+ time_t start = time(0);
+ const uint32_t timeout = 4;
+ {
+ // make sure i timeout before the messenger reconnects to the OSD,
+ // it will resend a watch request on behalf of the client, and the
+ // timer of timeout on OSD side will be reset by the new request.
+ char conf[128];
+ ASSERT_EQ(0, rados_conf_get(cluster,
+ "ms_connection_idle_timeout",
+ conf, sizeof(conf)));
+ auto connection_idle_timeout = std::stoll(conf);
+ ASSERT_LT(timeout, connection_idle_timeout);
+ }
+ ASSERT_EQ(0,
+ rados_watch3(ioctx, notify_oid, &handle,
+ watch_notify2_test_cb, watch_notify2_test_errcb,
+ timeout, this));
+ int age = rados_watch_check(ioctx, handle);
+ time_t age_bound = time(0) + 1 - start;
+ ASSERT_LT(age, age_bound * 1000);
+ ASSERT_GT(age, 0);
+ rados_conf_set(cluster, "objecter_inject_no_watch_ping", "true");
+ // allow a long time here since an osd peering event will renew our
+ // watch.
+ int left = 256 * timeout;
+ std::cout << "waiting up to " << left << " for osd to time us out ..."
+ << std::endl;
+ while (notify_err == 0 && --left) {
+ sleep(1);
+ }
+ ASSERT_GT(left, 0);
+ rados_conf_set(cluster, "objecter_inject_no_watch_ping", "false");
+ ASSERT_EQ(-ENOTCONN, notify_err);
+ ASSERT_EQ(-ENOTCONN, rados_watch_check(ioctx, handle));
+
+ // a subsequent notify should not reach us
+ char *reply_buf = nullptr;
+ size_t reply_buf_len;
+ ASSERT_EQ(0, rados_notify2(ioctx, notify_oid,
+ "notify", 6, 300000,
+ &reply_buf, &reply_buf_len));
+ {
+ bufferlist reply;
+ reply.append(reply_buf, reply_buf_len);
+ std::map<std::pair<uint64_t,uint64_t>, bufferlist> reply_map;
+ std::set<std::pair<uint64_t,uint64_t> > missed_map;
+ auto reply_p = reply.cbegin();
+ decode(reply_map, reply_p);
+ decode(missed_map, reply_p);
+ ASSERT_EQ(0u, reply_map.size());
+ ASSERT_EQ(0u, missed_map.size());
+ }
+ ASSERT_EQ(0u, notify_cookies.size());
+ ASSERT_EQ(-ENOTCONN, rados_watch_check(ioctx, handle));
+ rados_buffer_free(reply_buf);
+
+ // re-watch
+ rados_unwatch2(ioctx, handle);
+ rados_watch_flush(cluster);
+
+ handle = 0;
+ ASSERT_EQ(0,
+ rados_watch2(ioctx, notify_oid, &handle,
+ watch_notify2_test_cb,
+ watch_notify2_test_errcb, this));
+ ASSERT_GT(rados_watch_check(ioctx, handle), 0);
+
+ // and now a notify will work.
+ ASSERT_EQ(0, rados_notify2(ioctx, notify_oid,
+ "notify", 6, 300000,
+ &reply_buf, &reply_buf_len));
+ {
+ bufferlist reply;
+ reply.append(reply_buf, reply_buf_len);
+ std::map<std::pair<uint64_t,uint64_t>, bufferlist> reply_map;
+ std::set<std::pair<uint64_t,uint64_t> > missed_map;
+ auto reply_p = reply.cbegin();
+ decode(reply_map, reply_p);
+ decode(missed_map, reply_p);
+ ASSERT_EQ(1u, reply_map.size());
+ ASSERT_EQ(0u, missed_map.size());
+ ASSERT_EQ(1u, notify_cookies.count(handle));
+ ASSERT_EQ(5u, reply_map.begin()->second.length());
+ ASSERT_EQ(0, strncmp("reply", reply_map.begin()->second.c_str(), 5));
+ }
+ ASSERT_EQ(1u, notify_cookies.size());
+ ASSERT_GT(rados_watch_check(ioctx, handle), 0);
+
+ rados_buffer_free(reply_buf);
+ rados_unwatch2(ioctx, handle);
+ rados_watch_flush(cluster);
+}
+
+TEST_F(LibRadosWatchNotify, AioWatchDelete2) {
+ notify_io = ioctx;
+ notify_oid = "foo";
+ notify_err = 0;
+ char buf[128];
+ uint32_t timeout = 3;
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_write(ioctx, notify_oid, buf, sizeof(buf), 0));
+
+
+ rados_completion_t comp;
+ uint64_t handle;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &comp));
+ rados_aio_watch2(ioctx, notify_oid, comp, &handle,
+ watch_notify2_test_cb, watch_notify2_test_errcb, timeout, this);
+ ASSERT_EQ(0, rados_aio_wait_for_complete(comp));
+ ASSERT_EQ(0, rados_aio_get_return_value(comp));
+ rados_aio_release(comp);
+ ASSERT_EQ(0, rados_remove(ioctx, notify_oid));
+ int left = 30;
+ std::cout << "waiting up to " << left << " for disconnect notification ..."
+ << std::endl;
+ while (notify_err == 0 && --left) {
+ sleep(1);
+ }
+ ASSERT_TRUE(left > 0);
+ ASSERT_EQ(-ENOTCONN, notify_err);
+ int rados_watch_check_err = rados_watch_check(ioctx, handle);
+ // We may hit ENOENT due to socket failure injection and a forced reconnect
+ EXPECT_TRUE(rados_watch_check_err == -ENOTCONN || rados_watch_check_err == -ENOENT)
+ << "Where rados_watch_check_err = " << rados_watch_check_err;
+ ASSERT_EQ(0, rados_aio_create_completion2(nullptr, nullptr, &comp));
+ rados_aio_unwatch(ioctx, handle, comp);
+ ASSERT_EQ(0, rados_aio_wait_for_complete(comp));
+ ASSERT_EQ(-ENOENT, rados_aio_get_return_value(comp));
+ rados_aio_release(comp);
+}
diff --git a/src/test/librados/watch_notify_cxx.cc b/src/test/librados/watch_notify_cxx.cc
new file mode 100644
index 000000000..808384bcc
--- /dev/null
+++ b/src/test/librados/watch_notify_cxx.cc
@@ -0,0 +1,416 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <semaphore.h>
+#include <set>
+#include <map>
+
+#include "gtest/gtest.h"
+
+#include "include/encoding.h"
+#include "include/rados/librados.hpp"
+#include "include/rados/rados_types.h"
+#include "test/librados/test_cxx.h"
+#include "test/librados/testcase_cxx.h"
+#include "crimson_utils.h"
+
+using namespace librados;
+
+typedef RadosTestECPP LibRadosWatchNotifyECPP;
+
+int notify_sleep = 0;
+
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+class LibRadosWatchNotifyPP : public RadosTestParamPP
+{
+protected:
+ bufferlist notify_bl;
+ std::set<uint64_t> notify_cookies;
+ rados_ioctx_t notify_io;
+ const char *notify_oid = nullptr;
+ int notify_err = 0;
+
+ friend class WatchNotifyTestCtx2;
+ friend class WatchNotifyTestCtx2TimeOut;
+};
+
+IoCtx *notify_ioctx;
+
+class WatchNotifyTestCtx2 : public WatchCtx2
+{
+ LibRadosWatchNotifyPP *notify;
+
+public:
+ WatchNotifyTestCtx2(LibRadosWatchNotifyPP *notify)
+ : notify(notify)
+ {}
+
+ void handle_notify(uint64_t notify_id, uint64_t cookie, uint64_t notifier_gid,
+ bufferlist& bl) override {
+ std::cout << __func__ << " cookie " << cookie << " notify_id " << notify_id
+ << " notifier_gid " << notifier_gid << std::endl;
+ notify->notify_bl = bl;
+ notify->notify_cookies.insert(cookie);
+ bufferlist reply;
+ reply.append("reply", 5);
+ if (notify_sleep)
+ sleep(notify_sleep);
+ notify_ioctx->notify_ack(notify->notify_oid, notify_id, cookie, reply);
+ }
+
+ void handle_error(uint64_t cookie, int err) override {
+ std::cout << __func__ << " cookie " << cookie
+ << " err " << err << std::endl;
+ ceph_assert(cookie > 1000);
+ notify_ioctx->unwatch2(cookie);
+ notify->notify_cookies.erase(cookie);
+ notify->notify_err = notify_ioctx->watch2(notify->notify_oid, &cookie, this);
+ if (notify->notify_err < err ) {
+ std::cout << "reconnect notify_err " << notify->notify_err << " err " << err << std::endl;
+ }
+ }
+};
+
+class WatchNotifyTestCtx2TimeOut : public WatchCtx2
+{
+ LibRadosWatchNotifyPP *notify;
+
+public:
+ WatchNotifyTestCtx2TimeOut(LibRadosWatchNotifyPP *notify)
+ : notify(notify)
+ {}
+
+ void handle_notify(uint64_t notify_id, uint64_t cookie, uint64_t notifier_gid,
+ bufferlist& bl) override {
+ std::cout << __func__ << " cookie " << cookie << " notify_id " << notify_id
+ << " notifier_gid " << notifier_gid << std::endl;
+ notify->notify_bl = bl;
+ notify->notify_cookies.insert(cookie);
+ bufferlist reply;
+ reply.append("reply", 5);
+ if (notify_sleep)
+ sleep(notify_sleep);
+ notify_ioctx->notify_ack(notify->notify_oid, notify_id, cookie, reply);
+ }
+
+ void handle_error(uint64_t cookie, int err) override {
+ std::cout << __func__ << " cookie " << cookie
+ << " err " << err << std::endl;
+ ceph_assert(cookie > 1000);
+ notify->notify_err = err;
+ }
+};
+
+// notify
+static sem_t sem;
+
+class WatchNotifyTestCtx : public WatchCtx
+{
+public:
+ void notify(uint8_t opcode, uint64_t ver, bufferlist& bl) override
+ {
+ std::cout << __func__ << std::endl;
+ sem_post(&sem);
+ }
+};
+
+TEST_P(LibRadosWatchNotifyPP, WatchNotify) {
+ ASSERT_EQ(0, sem_init(&sem, 0, 0));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ uint64_t handle;
+ WatchNotifyTestCtx ctx;
+ ASSERT_EQ(0, ioctx.watch("foo", 0, &handle, &ctx));
+ std::list<obj_watch_t> watches;
+ ASSERT_EQ(0, ioctx.list_watchers("foo", &watches));
+ ASSERT_EQ(1u, watches.size());
+ bufferlist bl2;
+ for (unsigned i=0; i<10; ++i) {
+ int r = ioctx.notify("foo", 0, bl2);
+ if (r == 0) {
+ break;
+ }
+ if (!getenv("ALLOW_TIMEOUTS")) {
+ ASSERT_EQ(0, r);
+ }
+ }
+ TestAlarm alarm;
+ sem_wait(&sem);
+ ioctx.unwatch("foo", handle);
+ sem_destroy(&sem);
+}
+
+TEST_F(LibRadosWatchNotifyECPP, WatchNotify) {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, sem_init(&sem, 0, 0));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+ uint64_t handle;
+ WatchNotifyTestCtx ctx;
+ ASSERT_EQ(0, ioctx.watch("foo", 0, &handle, &ctx));
+ std::list<obj_watch_t> watches;
+ ASSERT_EQ(0, ioctx.list_watchers("foo", &watches));
+ ASSERT_EQ(1u, watches.size());
+ bufferlist bl2;
+ for (unsigned i=0; i<10; ++i) {
+ int r = ioctx.notify("foo", 0, bl2);
+ if (r == 0) {
+ break;
+ }
+ if (!getenv("ALLOW_TIMEOUTS")) {
+ ASSERT_EQ(0, r);
+ }
+ }
+ TestAlarm alarm;
+ sem_wait(&sem);
+ ioctx.unwatch("foo", handle);
+ sem_destroy(&sem);
+}
+
+// --
+
+TEST_P(LibRadosWatchNotifyPP, WatchNotifyTimeout) {
+ ASSERT_EQ(0, sem_init(&sem, 0, 0));
+ ioctx.set_notify_timeout(1);
+ uint64_t handle;
+ WatchNotifyTestCtx ctx;
+
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+
+ ASSERT_EQ(0, ioctx.watch("foo", 0, &handle, &ctx));
+ sem_destroy(&sem);
+ ASSERT_EQ(0, ioctx.unwatch("foo", handle));
+}
+
+TEST_F(LibRadosWatchNotifyECPP, WatchNotifyTimeout) {
+ SKIP_IF_CRIMSON();
+ ASSERT_EQ(0, sem_init(&sem, 0, 0));
+ ioctx.set_notify_timeout(1);
+ uint64_t handle;
+ WatchNotifyTestCtx ctx;
+
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0));
+
+ ASSERT_EQ(0, ioctx.watch("foo", 0, &handle, &ctx));
+ sem_destroy(&sem);
+ ASSERT_EQ(0, ioctx.unwatch("foo", handle));
+}
+
+#pragma GCC diagnostic pop
+#pragma GCC diagnostic warning "-Wpragmas"
+
+TEST_P(LibRadosWatchNotifyPP, WatchNotify2) {
+ notify_oid = "foo";
+ notify_ioctx = &ioctx;
+ notify_cookies.clear();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write(notify_oid, bl1, sizeof(buf), 0));
+ uint64_t handle;
+ WatchNotifyTestCtx2 ctx(this);
+ ASSERT_EQ(0, ioctx.watch2(notify_oid, &handle, &ctx));
+ ASSERT_GT(ioctx.watch_check(handle), 0);
+ std::list<obj_watch_t> watches;
+ ASSERT_EQ(0, ioctx.list_watchers(notify_oid, &watches));
+ ASSERT_EQ(watches.size(), 1u);
+ bufferlist bl2, bl_reply;
+ ASSERT_EQ(0, ioctx.notify2(notify_oid, bl2, 300000, &bl_reply));
+ auto p = bl_reply.cbegin();
+ std::map<std::pair<uint64_t,uint64_t>,bufferlist> reply_map;
+ std::set<std::pair<uint64_t,uint64_t> > missed_map;
+ decode(reply_map, p);
+ decode(missed_map, p);
+ ASSERT_EQ(1u, notify_cookies.size());
+ ASSERT_EQ(1u, notify_cookies.count(handle));
+ ASSERT_EQ(1u, reply_map.size());
+ ASSERT_EQ(5u, reply_map.begin()->second.length());
+ ASSERT_EQ(0, strncmp("reply", reply_map.begin()->second.c_str(), 5));
+ ASSERT_EQ(0u, missed_map.size());
+ ASSERT_GT(ioctx.watch_check(handle), 0);
+ ioctx.unwatch2(handle);
+}
+
+TEST_P(LibRadosWatchNotifyPP, AioWatchNotify2) {
+ notify_oid = "foo";
+ notify_ioctx = &ioctx;
+ notify_cookies.clear();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write(notify_oid, bl1, sizeof(buf), 0));
+
+ uint64_t handle;
+ WatchNotifyTestCtx2 ctx(this);
+ librados::AioCompletion *comp = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_watch(notify_oid, comp, &handle, &ctx));
+ ASSERT_EQ(0, comp->wait_for_complete());
+ ASSERT_EQ(0, comp->get_return_value());
+ comp->release();
+
+ ASSERT_GT(ioctx.watch_check(handle), 0);
+ std::list<obj_watch_t> watches;
+ ASSERT_EQ(0, ioctx.list_watchers(notify_oid, &watches));
+ ASSERT_EQ(watches.size(), 1u);
+ bufferlist bl2, bl_reply;
+ ASSERT_EQ(0, ioctx.notify2(notify_oid, bl2, 300000, &bl_reply));
+ auto p = bl_reply.cbegin();
+ std::map<std::pair<uint64_t,uint64_t>,bufferlist> reply_map;
+ std::set<std::pair<uint64_t,uint64_t> > missed_map;
+ decode(reply_map, p);
+ decode(missed_map, p);
+ ASSERT_EQ(1u, notify_cookies.size());
+ ASSERT_EQ(1u, notify_cookies.count(handle));
+ ASSERT_EQ(1u, reply_map.size());
+ ASSERT_EQ(5u, reply_map.begin()->second.length());
+ ASSERT_EQ(0, strncmp("reply", reply_map.begin()->second.c_str(), 5));
+ ASSERT_EQ(0u, missed_map.size());
+ ASSERT_GT(ioctx.watch_check(handle), 0);
+
+ comp = cluster.aio_create_completion();
+ ioctx.aio_unwatch(handle, comp);
+ ASSERT_EQ(0, comp->wait_for_complete());
+ comp->release();
+}
+
+
+TEST_P(LibRadosWatchNotifyPP, AioNotify) {
+ notify_oid = "foo";
+ notify_ioctx = &ioctx;
+ notify_cookies.clear();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write(notify_oid, bl1, sizeof(buf), 0));
+ uint64_t handle;
+ WatchNotifyTestCtx2 ctx(this);
+ ASSERT_EQ(0, ioctx.watch2(notify_oid, &handle, &ctx));
+ ASSERT_GT(ioctx.watch_check(handle), 0);
+ std::list<obj_watch_t> watches;
+ ASSERT_EQ(0, ioctx.list_watchers(notify_oid, &watches));
+ ASSERT_EQ(watches.size(), 1u);
+ bufferlist bl2, bl_reply;
+ librados::AioCompletion *comp = cluster.aio_create_completion();
+ ASSERT_EQ(0, ioctx.aio_notify(notify_oid, comp, bl2, 300000, &bl_reply));
+ ASSERT_EQ(0, comp->wait_for_complete());
+ ASSERT_EQ(0, comp->get_return_value());
+ comp->release();
+ std::vector<librados::notify_ack_t> acks;
+ std::vector<librados::notify_timeout_t> timeouts;
+ ioctx.decode_notify_response(bl_reply, &acks, &timeouts);
+ ASSERT_EQ(1u, notify_cookies.size());
+ ASSERT_EQ(1u, notify_cookies.count(handle));
+ ASSERT_EQ(1u, acks.size());
+ ASSERT_EQ(5u, acks[0].payload_bl.length());
+ ASSERT_EQ(0, strncmp("reply", acks[0].payload_bl.c_str(), acks[0].payload_bl.length()));
+ ASSERT_EQ(0u, timeouts.size());
+ ASSERT_GT(ioctx.watch_check(handle), 0);
+ ioctx.unwatch2(handle);
+ cluster.watch_flush();
+}
+
+// --
+TEST_P(LibRadosWatchNotifyPP, WatchNotify2Timeout) {
+ notify_oid = "foo";
+ notify_ioctx = &ioctx;
+ notify_sleep = 3; // 3s
+ notify_cookies.clear();
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write(notify_oid, bl1, sizeof(buf), 0));
+ uint64_t handle;
+ WatchNotifyTestCtx2TimeOut ctx(this);
+ ASSERT_EQ(0, ioctx.watch2(notify_oid, &handle, &ctx));
+ ASSERT_GT(ioctx.watch_check(handle), 0);
+ std::list<obj_watch_t> watches;
+ ASSERT_EQ(0, ioctx.list_watchers(notify_oid, &watches));
+ ASSERT_EQ(watches.size(), 1u);
+ ASSERT_EQ(0u, notify_cookies.size());
+ bufferlist bl2, bl_reply;
+ std::cout << " trying..." << std::endl;
+ ASSERT_EQ(-ETIMEDOUT, ioctx.notify2(notify_oid, bl2, 1000 /* 1s */,
+ &bl_reply));
+ std::cout << " timed out" << std::endl;
+ ASSERT_GT(ioctx.watch_check(handle), 0);
+ ioctx.unwatch2(handle);
+
+ std::cout << " flushing" << std::endl;
+ librados::AioCompletion *comp = cluster.aio_create_completion();
+ cluster.aio_watch_flush(comp);
+ ASSERT_EQ(0, comp->wait_for_complete());
+ ASSERT_EQ(0, comp->get_return_value());
+ std::cout << " flushed" << std::endl;
+ comp->release();
+}
+
+TEST_P(LibRadosWatchNotifyPP, WatchNotify3) {
+ notify_oid = "foo";
+ notify_ioctx = &ioctx;
+ notify_cookies.clear();
+ uint32_t timeout = 12; // configured timeout
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write(notify_oid, bl1, sizeof(buf), 0));
+ uint64_t handle;
+ WatchNotifyTestCtx2TimeOut ctx(this);
+ ASSERT_EQ(0, ioctx.watch3(notify_oid, &handle, &ctx, timeout));
+ ASSERT_GT(ioctx.watch_check(handle), 0);
+ std::list<obj_watch_t> watches;
+ ASSERT_EQ(0, ioctx.list_watchers(notify_oid, &watches));
+ ASSERT_EQ(watches.size(), 1u);
+ std::cout << "List watches" << std::endl;
+ for (std::list<obj_watch_t>::iterator it = watches.begin();
+ it != watches.end(); ++it) {
+ ASSERT_EQ(it->timeout_seconds, timeout);
+ }
+ bufferlist bl2, bl_reply;
+ std::cout << "notify2" << std::endl;
+ ASSERT_EQ(0, ioctx.notify2(notify_oid, bl2, 300000, &bl_reply));
+ std::cout << "notify2 done" << std::endl;
+ auto p = bl_reply.cbegin();
+ std::map<std::pair<uint64_t,uint64_t>,bufferlist> reply_map;
+ std::set<std::pair<uint64_t,uint64_t> > missed_map;
+ decode(reply_map, p);
+ decode(missed_map, p);
+ ASSERT_EQ(1u, notify_cookies.size());
+ ASSERT_EQ(1u, notify_cookies.count(handle));
+ ASSERT_EQ(1u, reply_map.size());
+ ASSERT_EQ(5u, reply_map.begin()->second.length());
+ ASSERT_EQ(0, strncmp("reply", reply_map.begin()->second.c_str(), 5));
+ ASSERT_EQ(0u, missed_map.size());
+ std::cout << "watch_check" << std::endl;
+ ASSERT_GT(ioctx.watch_check(handle), 0);
+ std::cout << "unwatch2" << std::endl;
+ ioctx.unwatch2(handle);
+
+ std::cout << " flushing" << std::endl;
+ cluster.watch_flush();
+ std::cout << "done" << std::endl;
+}
+// --
+
+INSTANTIATE_TEST_SUITE_P(LibRadosWatchNotifyPPTests, LibRadosWatchNotifyPP,
+ ::testing::Values("", "cache"));
diff --git a/src/test/librados_test_stub/CMakeLists.txt b/src/test/librados_test_stub/CMakeLists.txt
new file mode 100644
index 000000000..f501d2da2
--- /dev/null
+++ b/src/test/librados_test_stub/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(librados_test_stub_srcs
+ LibradosTestStub.cc
+ NeoradosTestStub.cc
+ TestClassHandler.cc
+ TestIoCtxImpl.cc
+ TestMemCluster.cc
+ TestMemIoCtxImpl.cc
+ TestMemRadosClient.cc
+ TestRadosClient.cc
+ TestWatchNotify.cc)
+add_library(rados_test_stub STATIC ${librados_test_stub_srcs})
+
diff --git a/src/test/librados_test_stub/LibradosTestStub.cc b/src/test/librados_test_stub/LibradosTestStub.cc
new file mode 100644
index 000000000..238cffa19
--- /dev/null
+++ b/src/test/librados_test_stub/LibradosTestStub.cc
@@ -0,0 +1,1568 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librados_test_stub/LibradosTestStub.h"
+#include "include/rados/librados.hpp"
+#include "include/stringify.h"
+#include "common/ceph_argparse.h"
+#include "common/ceph_context.h"
+#include "common/common_init.h"
+#include "common/config.h"
+#include "common/debug.h"
+#include "common/snap_types.h"
+#include "librados/AioCompletionImpl.h"
+#include "log/Log.h"
+#include "test/librados_test_stub/TestClassHandler.h"
+#include "test/librados_test_stub/TestIoCtxImpl.h"
+#include "test/librados_test_stub/TestRadosClient.h"
+#include "test/librados_test_stub/TestMemCluster.h"
+#include "test/librados_test_stub/TestMemRadosClient.h"
+#include "objclass/objclass.h"
+#include "osd/osd_types.h"
+#include <arpa/inet.h>
+#include <boost/shared_ptr.hpp>
+#include <deque>
+#include <functional>
+#include <list>
+#include <vector>
+#include "include/ceph_assert.h"
+#include "include/compat.h"
+
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_rados
+
+using namespace std;
+
+namespace librados {
+
+MockTestMemIoCtxImpl &get_mock_io_ctx(IoCtx &ioctx) {
+ MockTestMemIoCtxImpl **mock =
+ reinterpret_cast<MockTestMemIoCtxImpl **>(&ioctx);
+ return **mock;
+}
+
+} // namespace librados
+
+namespace librados_test_stub {
+
+TestClusterRef &cluster() {
+ static TestClusterRef s_cluster;
+ return s_cluster;
+}
+
+void set_cluster(TestClusterRef cluster_ref) {
+ cluster() = cluster_ref;
+}
+
+TestClusterRef get_cluster() {
+ auto &cluster_ref = cluster();
+ if (cluster_ref.get() == nullptr) {
+ cluster_ref.reset(new librados::TestMemCluster());
+ }
+ return cluster_ref;
+}
+
+librados::TestClassHandler *get_class_handler() {
+ static boost::shared_ptr<librados::TestClassHandler> s_class_handler;
+ if (!s_class_handler) {
+ s_class_handler.reset(new librados::TestClassHandler());
+ s_class_handler->open_all_classes();
+ }
+ return s_class_handler.get();
+}
+
+} // namespace librados_test_stub
+
+namespace {
+
+void do_out_buffer(bufferlist& outbl, char **outbuf, size_t *outbuflen) {
+ if (outbuf) {
+ if (outbl.length() > 0) {
+ *outbuf = (char *)malloc(outbl.length());
+ memcpy(*outbuf, outbl.c_str(), outbl.length());
+ } else {
+ *outbuf = NULL;
+ }
+ }
+ if (outbuflen) {
+ *outbuflen = outbl.length();
+ }
+}
+
+void do_out_buffer(string& outbl, char **outbuf, size_t *outbuflen) {
+ if (outbuf) {
+ if (outbl.length() > 0) {
+ *outbuf = (char *)malloc(outbl.length());
+ memcpy(*outbuf, outbl.c_str(), outbl.length());
+ } else {
+ *outbuf = NULL;
+ }
+ }
+ if (outbuflen) {
+ *outbuflen = outbl.length();
+ }
+}
+
+librados::TestRadosClient *create_rados_client() {
+ CephInitParameters iparams(CEPH_ENTITY_TYPE_CLIENT);
+ CephContext *cct = common_preinit(iparams, CODE_ENVIRONMENT_LIBRARY, 0);
+ cct->_conf.parse_env(cct->get_module_type());
+ cct->_conf.apply_changes(nullptr);
+ cct->_log->start();
+
+ auto rados_client =
+ librados_test_stub::get_cluster()->create_rados_client(cct);
+ cct->put();
+ return rados_client;
+}
+
+} // anonymous namespace
+
+extern "C" int rados_aio_create_completion2(void *cb_arg,
+ rados_callback_t cb_complete,
+ rados_completion_t *pc)
+{
+ librados::AioCompletionImpl *c = new librados::AioCompletionImpl;
+ if (cb_complete) {
+ c->set_complete_callback(cb_arg, cb_complete);
+ }
+ *pc = c;
+ return 0;
+}
+
+extern "C" int rados_aio_get_return_value(rados_completion_t c) {
+ return reinterpret_cast<librados::AioCompletionImpl*>(c)->get_return_value();
+}
+
+extern "C" rados_config_t rados_cct(rados_t cluster)
+{
+ librados::TestRadosClient *client =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+ return reinterpret_cast<rados_config_t>(client->cct());
+}
+
+extern "C" int rados_conf_set(rados_t cluster, const char *option,
+ const char *value) {
+ librados::TestRadosClient *impl =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+ CephContext *cct = impl->cct();
+ return cct->_conf.set_val(option, value);
+}
+
+extern "C" int rados_conf_parse_env(rados_t cluster, const char *var) {
+ librados::TestRadosClient *client =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+ auto& conf = client->cct()->_conf;
+ conf.parse_env(client->cct()->get_module_type(), var);
+ conf.apply_changes(NULL);
+ return 0;
+}
+
+extern "C" int rados_conf_read_file(rados_t cluster, const char *path) {
+ librados::TestRadosClient *client =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+ auto& conf = client->cct()->_conf;
+ int ret = conf.parse_config_files(path, NULL, 0);
+ if (ret == 0) {
+ conf.parse_env(client->cct()->get_module_type());
+ conf.apply_changes(NULL);
+ conf.complain_about_parse_error(client->cct());
+ } else if (ret == -ENOENT) {
+ // ignore missing client config
+ return 0;
+ }
+ return ret;
+}
+
+extern "C" int rados_connect(rados_t cluster) {
+ librados::TestRadosClient *client =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+ return client->connect();
+}
+
+extern "C" int rados_create(rados_t *cluster, const char * const id) {
+ *cluster = create_rados_client();
+ return 0;
+}
+
+extern "C" int rados_create_with_context(rados_t *cluster,
+ rados_config_t cct_) {
+ auto cct = reinterpret_cast<CephContext*>(cct_);
+ *cluster = librados_test_stub::get_cluster()->create_rados_client(cct);
+ return 0;
+}
+
+extern "C" rados_config_t rados_ioctx_cct(rados_ioctx_t ioctx)
+{
+ librados::TestIoCtxImpl *ctx =
+ reinterpret_cast<librados::TestIoCtxImpl*>(ioctx);
+ return reinterpret_cast<rados_config_t>(ctx->get_rados_client()->cct());
+}
+
+extern "C" int rados_ioctx_create(rados_t cluster, const char *pool_name,
+ rados_ioctx_t *ioctx) {
+ librados::TestRadosClient *client =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+
+ int64_t pool_id = client->pool_lookup(pool_name);
+ if (pool_id < 0) {
+ return static_cast<int>(pool_id);
+ }
+
+ *ioctx = reinterpret_cast<rados_ioctx_t>(
+ client->create_ioctx(pool_id, pool_name));
+ return 0;
+}
+
+extern "C" int rados_ioctx_create2(rados_t cluster, int64_t pool_id,
+ rados_ioctx_t *ioctx)
+{
+ librados::TestRadosClient *client =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+
+ std::list<std::pair<int64_t, std::string> > pools;
+ int r = client->pool_list(pools);
+ if (r < 0) {
+ return r;
+ }
+
+ for (std::list<std::pair<int64_t, std::string> >::iterator it =
+ pools.begin(); it != pools.end(); ++it) {
+ if (it->first == pool_id) {
+ *ioctx = reinterpret_cast<rados_ioctx_t>(
+ client->create_ioctx(pool_id, it->second));
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+extern "C" void rados_ioctx_destroy(rados_ioctx_t io) {
+ librados::TestIoCtxImpl *ctx =
+ reinterpret_cast<librados::TestIoCtxImpl*>(io);
+ ctx->put();
+}
+
+extern "C" rados_t rados_ioctx_get_cluster(rados_ioctx_t io) {
+ librados::TestIoCtxImpl *ctx =
+ reinterpret_cast<librados::TestIoCtxImpl*>(io);
+ return reinterpret_cast<rados_t>(ctx->get_rados_client());
+}
+
+extern "C" int rados_mon_command(rados_t cluster, const char **cmd,
+ size_t cmdlen, const char *inbuf,
+ size_t inbuflen, char **outbuf,
+ size_t *outbuflen, char **outs,
+ size_t *outslen) {
+ librados::TestRadosClient *client =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+
+ vector<string> cmdvec;
+ for (size_t i = 0; i < cmdlen; i++) {
+ cmdvec.push_back(cmd[i]);
+ }
+
+ bufferlist inbl;
+ inbl.append(inbuf, inbuflen);
+
+ bufferlist outbl;
+ string outstring;
+ int ret = client->mon_command(cmdvec, inbl, &outbl, &outstring);
+
+ do_out_buffer(outbl, outbuf, outbuflen);
+ do_out_buffer(outstring, outs, outslen);
+ return ret;
+}
+
+extern "C" int rados_nobjects_list_open(rados_ioctx_t io,
+ rados_list_ctx_t *ctx) {
+ librados::TestIoCtxImpl *io_ctx =
+ reinterpret_cast<librados::TestIoCtxImpl*>(io);
+ librados::TestRadosClient *client = io_ctx->get_rados_client();
+
+ std::list<librados::TestRadosClient::Object> *list =
+ new std::list<librados::TestRadosClient::Object>();
+
+ client->object_list(io_ctx->get_id(), list);
+ list->push_front(librados::TestRadosClient::Object());
+ *ctx = reinterpret_cast<rados_list_ctx_t>(list);
+ return 0;
+}
+
+extern "C" int rados_nobjects_list_next(rados_list_ctx_t ctx,
+ const char **entry,
+ const char **key,
+ const char **nspace) {
+ std::list<librados::TestRadosClient::Object> *list =
+ reinterpret_cast<std::list<librados::TestRadosClient::Object> *>(ctx);
+ if (!list->empty()) {
+ list->pop_front();
+ }
+ if (list->empty()) {
+ return -ENOENT;
+ }
+
+ librados::TestRadosClient::Object &obj = list->front();
+ if (entry != NULL) {
+ *entry = obj.oid.c_str();
+ }
+ if (key != NULL) {
+ *key = obj.locator.c_str();
+ }
+ if (nspace != NULL) {
+ *nspace = obj.nspace.c_str();
+ }
+ return 0;
+}
+
+extern "C" void rados_nobjects_list_close(rados_list_ctx_t ctx) {
+ std::list<librados::TestRadosClient::Object> *list =
+ reinterpret_cast<std::list<librados::TestRadosClient::Object> *>(ctx);
+ delete list;
+}
+
+extern "C" int rados_pool_create(rados_t cluster, const char *pool_name) {
+ librados::TestRadosClient *client =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+ return client->pool_create(pool_name);
+}
+
+extern "C" int rados_pool_delete(rados_t cluster, const char *pool_name) {
+ librados::TestRadosClient *client =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+ return client->pool_delete(pool_name);
+}
+
+extern "C" void rados_shutdown(rados_t cluster) {
+ librados::TestRadosClient *client =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+ client->put();
+}
+
+extern "C" int rados_wait_for_latest_osdmap(rados_t cluster) {
+ librados::TestRadosClient *client =
+ reinterpret_cast<librados::TestRadosClient*>(cluster);
+ return client->wait_for_latest_osdmap();
+}
+
+using namespace std::placeholders;
+
+namespace librados {
+
+AioCompletion::~AioCompletion()
+{
+ auto c = reinterpret_cast<AioCompletionImpl *>(pc);
+ c->release();
+}
+
+void AioCompletion::release() {
+ delete this;
+}
+
+IoCtx::IoCtx() : io_ctx_impl(NULL) {
+}
+
+IoCtx::~IoCtx() {
+ close();
+}
+
+IoCtx::IoCtx(const IoCtx& rhs) {
+ io_ctx_impl = rhs.io_ctx_impl;
+ if (io_ctx_impl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->get();
+ }
+}
+
+IoCtx::IoCtx(IoCtx&& rhs) noexcept : io_ctx_impl(std::exchange(rhs.io_ctx_impl, nullptr))
+{
+}
+
+IoCtx& IoCtx::operator=(const IoCtx& rhs) {
+ if (io_ctx_impl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->put();
+ }
+
+ io_ctx_impl = rhs.io_ctx_impl;
+ if (io_ctx_impl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->get();
+ }
+ return *this;
+}
+
+librados::IoCtx& librados::IoCtx::operator=(IoCtx&& rhs) noexcept
+{
+ if (io_ctx_impl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->put();
+ }
+
+ io_ctx_impl = std::exchange(rhs.io_ctx_impl, nullptr);
+ return *this;
+}
+
+int IoCtx::aio_flush() {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->aio_flush();
+ return 0;
+}
+
+int IoCtx::aio_flush_async(AioCompletion *c) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->aio_flush_async(c->pc);
+ return 0;
+}
+
+int IoCtx::aio_notify(const std::string& oid, AioCompletion *c, bufferlist& bl,
+ uint64_t timeout_ms, bufferlist *pbl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->aio_notify(oid, c->pc, bl, timeout_ms, pbl);
+ return 0;
+}
+
+int IoCtx::aio_operate(const std::string& oid, AioCompletion *c,
+ ObjectReadOperation *op, bufferlist *pbl) {
+ return aio_operate(oid, c, op, 0, pbl);
+}
+
+int IoCtx::aio_operate(const std::string& oid, AioCompletion *c,
+ ObjectReadOperation *op, int flags,
+ bufferlist *pbl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ TestObjectOperationImpl *ops = reinterpret_cast<TestObjectOperationImpl*>(op->impl);
+ return ctx->aio_operate_read(oid, *ops, c->pc, flags, pbl,
+ ctx->get_snap_read(), nullptr);
+}
+
+int IoCtx::aio_operate(const std::string& oid, AioCompletion *c,
+ ObjectReadOperation *op, int flags,
+ bufferlist *pbl, const blkin_trace_info *trace_info) {
+ return aio_operate(oid, c, op, flags, pbl);
+}
+
+int IoCtx::aio_operate(const std::string& oid, AioCompletion *c,
+ ObjectWriteOperation *op) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ TestObjectOperationImpl *ops = reinterpret_cast<TestObjectOperationImpl*>(op->impl);
+ return ctx->aio_operate(oid, *ops, c->pc, nullptr, nullptr, 0);
+}
+
+int IoCtx::aio_operate(const std::string& oid, AioCompletion *c,
+ ObjectWriteOperation *op, snap_t seq,
+ std::vector<snap_t>& snaps, int flags,
+ const blkin_trace_info *trace_info) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ TestObjectOperationImpl *ops = reinterpret_cast<TestObjectOperationImpl*>(op->impl);
+
+ std::vector<snapid_t> snv;
+ snv.resize(snaps.size());
+ for (size_t i = 0; i < snaps.size(); ++i)
+ snv[i] = snaps[i];
+ SnapContext snapc(seq, snv);
+
+ return ctx->aio_operate(oid, *ops, c->pc, &snapc, nullptr, flags);
+}
+
+int IoCtx::aio_operate(const std::string& oid, AioCompletion *c,
+ ObjectWriteOperation *op, snap_t seq,
+ std::vector<snap_t>& snaps) {
+ return aio_operate(oid, c, op, seq, snaps, 0, nullptr);
+}
+
+int IoCtx::aio_operate(const std::string& oid, AioCompletion *c,
+ ObjectWriteOperation *op, snap_t seq,
+ std::vector<snap_t>& snaps,
+ const blkin_trace_info *trace_info) {
+ return aio_operate(oid, c, op, seq, snaps, 0, trace_info);
+}
+
+int IoCtx::aio_remove(const std::string& oid, AioCompletion *c) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->aio_remove(oid, c->pc);
+}
+
+int IoCtx::aio_remove(const std::string& oid, AioCompletion *c, int flags) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->aio_remove(oid, c->pc, flags);
+}
+
+int IoCtx::aio_watch(const std::string& o, AioCompletion *c, uint64_t *handle,
+ librados::WatchCtx2 *watch_ctx) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->aio_watch(o, c->pc, handle, watch_ctx);
+}
+
+int IoCtx::aio_unwatch(uint64_t handle, AioCompletion *c) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->aio_unwatch(handle, c->pc);
+}
+
+config_t IoCtx::cct() {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return reinterpret_cast<config_t>(ctx->get_rados_client()->cct());
+}
+
+void IoCtx::close() {
+ if (io_ctx_impl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->put();
+ }
+ io_ctx_impl = NULL;
+}
+
+int IoCtx::create(const std::string& oid, bool exclusive) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::create, _1, _2, exclusive,
+ ctx->get_snap_context()));
+}
+
+void IoCtx::dup(const IoCtx& rhs) {
+ close();
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(rhs.io_ctx_impl);
+ io_ctx_impl = reinterpret_cast<IoCtxImpl*>(ctx->clone());
+}
+
+int IoCtx::exec(const std::string& oid, const char *cls, const char *method,
+ bufferlist& inbl, bufferlist& outbl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::exec, _1, _2,
+ librados_test_stub::get_class_handler(), cls,
+ method, inbl, &outbl, ctx->get_snap_read(),
+ ctx->get_snap_context()));
+}
+
+void IoCtx::from_rados_ioctx_t(rados_ioctx_t p, IoCtx &io) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(p);
+ ctx->get();
+
+ io.close();
+ io.io_ctx_impl = reinterpret_cast<IoCtxImpl*>(ctx);
+}
+
+uint64_t IoCtx::get_instance_id() const {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->get_instance_id();
+}
+
+int64_t IoCtx::get_id() {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->get_id();
+}
+
+uint64_t IoCtx::get_last_version() {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->get_last_version();
+}
+
+std::string IoCtx::get_pool_name() {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->get_pool_name();
+}
+
+int IoCtx::list_snaps(const std::string& o, snap_set_t *out_snaps) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ o, std::bind(&TestIoCtxImpl::list_snaps, _1, _2, out_snaps));
+}
+
+int IoCtx::list_watchers(const std::string& o,
+ std::list<obj_watch_t> *out_watchers) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ o, std::bind(&TestIoCtxImpl::list_watchers, _1, _2, out_watchers));
+}
+
+int IoCtx::notify(const std::string& o, uint64_t ver, bufferlist& bl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->notify(o, bl, 0, NULL);
+}
+
+int IoCtx::notify2(const std::string& o, bufferlist& bl,
+ uint64_t timeout_ms, bufferlist *pbl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->notify(o, bl, timeout_ms, pbl);
+}
+
+void IoCtx::notify_ack(const std::string& o, uint64_t notify_id,
+ uint64_t handle, bufferlist& bl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->notify_ack(o, notify_id, handle, bl);
+}
+
+int IoCtx::omap_get_vals(const std::string& oid,
+ const std::string& start_after,
+ uint64_t max_return,
+ std::map<std::string, bufferlist> *out_vals) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::omap_get_vals, _1, _2, start_after, "",
+ max_return, out_vals));
+}
+
+int IoCtx::operate(const std::string& oid, ObjectWriteOperation *op) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ TestObjectOperationImpl *ops = reinterpret_cast<TestObjectOperationImpl*>(op->impl);
+ return ctx->operate(oid, *ops);
+}
+
+int IoCtx::operate(const std::string& oid, ObjectReadOperation *op,
+ bufferlist *pbl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ TestObjectOperationImpl *ops = reinterpret_cast<TestObjectOperationImpl*>(op->impl);
+ return ctx->operate_read(oid, *ops, pbl);
+}
+
+int IoCtx::read(const std::string& oid, bufferlist& bl, size_t len,
+ uint64_t off) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::read, _1, _2, len, off, &bl,
+ ctx->get_snap_read(), nullptr));
+}
+
+int IoCtx::remove(const std::string& oid) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::remove, _1, _2, ctx->get_snap_context()));
+}
+
+int IoCtx::selfmanaged_snap_create(uint64_t *snapid) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->selfmanaged_snap_create(snapid);
+}
+
+void IoCtx::aio_selfmanaged_snap_create(uint64_t *snapid, AioCompletion* c) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->aio_selfmanaged_snap_create(snapid, c->pc);
+}
+
+int IoCtx::selfmanaged_snap_remove(uint64_t snapid) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->selfmanaged_snap_remove(snapid);
+}
+
+void IoCtx::aio_selfmanaged_snap_remove(uint64_t snapid, AioCompletion* c) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->aio_selfmanaged_snap_remove(snapid, c->pc);
+}
+
+int IoCtx::selfmanaged_snap_rollback(const std::string& oid,
+ uint64_t snapid) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->selfmanaged_snap_rollback(oid, snapid);
+}
+
+int IoCtx::selfmanaged_snap_set_write_ctx(snap_t seq,
+ std::vector<snap_t>& snaps) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->selfmanaged_snap_set_write_ctx(seq, snaps);
+}
+
+void IoCtx::snap_set_read(snap_t seq) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->set_snap_read(seq);
+}
+
+int IoCtx::sparse_read(const std::string& oid, std::map<uint64_t,uint64_t>& m,
+ bufferlist& bl, size_t len, uint64_t off) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::sparse_read, _1, _2, off, len, &m, &bl,
+ ctx->get_snap_read()));
+}
+
+int IoCtx::stat(const std::string& oid, uint64_t *psize, time_t *pmtime) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::stat, _1, _2, psize, pmtime));
+}
+
+int IoCtx::tmap_update(const std::string& oid, bufferlist& cmdbl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::tmap_update, _1, _2, cmdbl));
+}
+
+int IoCtx::trunc(const std::string& oid, uint64_t off) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::truncate, _1, _2, off,
+ ctx->get_snap_context()));
+}
+
+int IoCtx::unwatch2(uint64_t handle) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->unwatch(handle);
+}
+
+int IoCtx::unwatch(const std::string& o, uint64_t handle) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->unwatch(handle);
+}
+
+int IoCtx::watch(const std::string& o, uint64_t ver, uint64_t *handle,
+ librados::WatchCtx *wctx) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->watch(o, handle, wctx, NULL);
+}
+
+int IoCtx::watch2(const std::string& o, uint64_t *handle,
+ librados::WatchCtx2 *wctx) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->watch(o, handle, NULL, wctx);
+}
+
+int IoCtx::write(const std::string& oid, bufferlist& bl, size_t len,
+ uint64_t off) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::write, _1, _2, bl, len, off,
+ ctx->get_snap_context()));
+}
+
+int IoCtx::write_full(const std::string& oid, bufferlist& bl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::write_full, _1, _2, bl,
+ ctx->get_snap_context()));
+}
+
+int IoCtx::writesame(const std::string& oid, bufferlist& bl, size_t len,
+ uint64_t off) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::writesame, _1, _2, bl, len, off,
+ ctx->get_snap_context()));
+}
+
+int IoCtx::cmpext(const std::string& oid, uint64_t off, bufferlist& cmp_bl) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->execute_operation(
+ oid, std::bind(&TestIoCtxImpl::cmpext, _1, _2, off, cmp_bl,
+ ctx->get_snap_read()));
+}
+
+int IoCtx::application_enable(const std::string& app_name, bool force) {
+ return 0;
+}
+
+int IoCtx::application_enable_async(const std::string& app_name,
+ bool force, PoolAsyncCompletion *c) {
+ return -EOPNOTSUPP;
+}
+
+int IoCtx::application_list(std::set<std::string> *app_names) {
+ return -EOPNOTSUPP;
+}
+
+int IoCtx::application_metadata_get(const std::string& app_name,
+ const std::string &key,
+ std::string *value) {
+ return -EOPNOTSUPP;
+}
+
+int IoCtx::application_metadata_set(const std::string& app_name,
+ const std::string &key,
+ const std::string& value) {
+ return -EOPNOTSUPP;
+}
+
+int IoCtx::application_metadata_remove(const std::string& app_name,
+ const std::string &key) {
+ return -EOPNOTSUPP;
+}
+
+int IoCtx::application_metadata_list(const std::string& app_name,
+ std::map<std::string, std::string> *values) {
+ return -EOPNOTSUPP;
+}
+
+void IoCtx::set_namespace(const std::string& nspace) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ ctx->set_namespace(nspace);
+}
+
+std::string IoCtx::get_namespace() const {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(io_ctx_impl);
+ return ctx->get_namespace();
+}
+
+void IoCtx::set_pool_full_try() {
+}
+
+bool IoCtx::get_pool_full_try() {
+ return false;
+}
+
+static int save_operation_result(int result, int *pval) {
+ if (pval != NULL) {
+ *pval = result;
+ }
+ return result;
+}
+
+ObjectOperation::ObjectOperation() {
+ TestObjectOperationImpl *o = new TestObjectOperationImpl();
+ o->get();
+ impl = reinterpret_cast<ObjectOperationImpl*>(o);
+}
+
+ObjectOperation::~ObjectOperation() {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ if (o) {
+ o->put();
+ o = NULL;
+ }
+}
+
+void ObjectOperation::assert_exists() {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::assert_exists, _1, _2, _4));
+}
+
+void ObjectOperation::assert_version(uint64_t ver) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::assert_version, _1, _2, ver));
+}
+
+void ObjectOperation::exec(const char *cls, const char *method,
+ bufferlist& inbl) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::exec, _1, _2,
+ librados_test_stub::get_class_handler(), cls,
+ method, inbl, _3, _4, _5));
+}
+
+void ObjectOperation::set_op_flags2(int flags) {
+}
+
+size_t ObjectOperation::size() {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ return o->ops.size();
+}
+
+void ObjectOperation::cmpext(uint64_t off, const bufferlist& cmp_bl,
+ int *prval) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ ObjectOperationTestImpl op = std::bind(&TestIoCtxImpl::cmpext, _1, _2, off,
+ cmp_bl, _4);
+ if (prval != NULL) {
+ op = std::bind(save_operation_result,
+ std::bind(op, _1, _2, _3, _4, _5, _6), prval);
+ }
+ o->ops.push_back(op);
+}
+
+void ObjectReadOperation::list_snaps(snap_set_t *out_snaps, int *prval) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+
+ ObjectOperationTestImpl op = std::bind(&TestIoCtxImpl::list_snaps, _1, _2,
+ out_snaps);
+ if (prval != NULL) {
+ op = std::bind(save_operation_result,
+ std::bind(op, _1, _2, _3, _4, _5, _6), prval);
+ }
+ o->ops.push_back(op);
+}
+
+void ObjectReadOperation::list_watchers(std::list<obj_watch_t> *out_watchers,
+ int *prval) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+
+ ObjectOperationTestImpl op = std::bind(&TestIoCtxImpl::list_watchers, _1,
+ _2, out_watchers);
+ if (prval != NULL) {
+ op = std::bind(save_operation_result,
+ std::bind(op, _1, _2, _3, _4, _5, _6), prval);
+ }
+ o->ops.push_back(op);
+}
+
+void ObjectReadOperation::read(size_t off, uint64_t len, bufferlist *pbl,
+ int *prval) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+
+ ObjectOperationTestImpl op;
+ if (pbl != NULL) {
+ op = std::bind(&TestIoCtxImpl::read, _1, _2, len, off, pbl, _4, nullptr);
+ } else {
+ op = std::bind(&TestIoCtxImpl::read, _1, _2, len, off, _3, _4, nullptr);
+ }
+
+ if (prval != NULL) {
+ op = std::bind(save_operation_result,
+ std::bind(op, _1, _2, _3, _4, _5, _6), prval);
+ }
+ o->ops.push_back(op);
+}
+
+void ObjectReadOperation::sparse_read(uint64_t off, uint64_t len,
+ std::map<uint64_t,uint64_t> *m,
+ bufferlist *pbl, int *prval,
+ uint64_t truncate_size,
+ uint32_t truncate_seq) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+
+ ObjectOperationTestImpl op;
+ if (pbl != NULL) {
+ op = std::bind(&TestIoCtxImpl::sparse_read, _1, _2, off, len, m, pbl, _4);
+ } else {
+ op = std::bind(&TestIoCtxImpl::sparse_read, _1, _2, off, len, m, _3, _4);
+ }
+
+ if (prval != NULL) {
+ op = std::bind(save_operation_result,
+ std::bind(op, _1, _2, _3, _4, _5, _6), prval);
+ }
+ o->ops.push_back(op);
+}
+
+void ObjectReadOperation::stat(uint64_t *psize, time_t *pmtime, int *prval) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+
+ ObjectOperationTestImpl op = std::bind(&TestIoCtxImpl::stat, _1, _2,
+ psize, pmtime);
+
+ if (prval != NULL) {
+ op = std::bind(save_operation_result,
+ std::bind(op, _1, _2, _3, _4, _5, _6), prval);
+ }
+ o->ops.push_back(op);
+}
+
+void ObjectWriteOperation::append(const bufferlist &bl) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::append, _1, _2, bl, _5));
+}
+
+void ObjectWriteOperation::create(bool exclusive) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::create, _1, _2, exclusive, _5));
+}
+
+void ObjectWriteOperation::omap_set(const std::map<std::string, bufferlist> &map) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::omap_set, _1, _2, boost::ref(map)));
+}
+
+void ObjectWriteOperation::remove() {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::remove, _1, _2, _5));
+}
+
+void ObjectWriteOperation::selfmanaged_snap_rollback(uint64_t snapid) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::selfmanaged_snap_rollback,
+ _1, _2, snapid));
+}
+
+void ObjectWriteOperation::set_alloc_hint(uint64_t expected_object_size,
+ uint64_t expected_write_size) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::set_alloc_hint, _1, _2,
+ expected_object_size, expected_write_size, 0,
+ _5));
+}
+
+void ObjectWriteOperation::set_alloc_hint2(uint64_t expected_object_size,
+ uint64_t expected_write_size,
+ uint32_t flags) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::set_alloc_hint, _1, _2,
+ expected_object_size, expected_write_size, flags,
+ _5));
+}
+
+void ObjectWriteOperation::tmap_update(const bufferlist& cmdbl) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::tmap_update, _1, _2,
+ cmdbl));
+}
+
+void ObjectWriteOperation::truncate(uint64_t off) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::truncate, _1, _2, off, _5));
+}
+
+void ObjectWriteOperation::write(uint64_t off, const bufferlist& bl) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::write, _1, _2, bl, bl.length(),
+ off, _5));
+}
+
+void ObjectWriteOperation::write_full(const bufferlist& bl) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::write_full, _1, _2, bl, _5));
+}
+
+void ObjectWriteOperation::writesame(uint64_t off, uint64_t len,
+ const bufferlist& bl) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::writesame, _1, _2, bl, len,
+ off, _5));
+}
+
+void ObjectWriteOperation::zero(uint64_t off, uint64_t len) {
+ TestObjectOperationImpl *o = reinterpret_cast<TestObjectOperationImpl*>(impl);
+ o->ops.push_back(std::bind(&TestIoCtxImpl::zero, _1, _2, off, len, _5));
+}
+
+Rados::Rados() : client(NULL) {
+}
+
+Rados::Rados(IoCtx& ioctx) {
+ TestIoCtxImpl *ctx = reinterpret_cast<TestIoCtxImpl*>(ioctx.io_ctx_impl);
+ TestRadosClient *impl = ctx->get_rados_client();
+ impl->get();
+
+ client = reinterpret_cast<RadosClient*>(impl);
+ ceph_assert(client != NULL);
+}
+
+Rados::~Rados() {
+ shutdown();
+}
+
+void Rados::from_rados_t(rados_t p, Rados &rados) {
+ if (rados.client != nullptr) {
+ reinterpret_cast<TestRadosClient*>(rados.client)->put();
+ rados.client = nullptr;
+ }
+
+ auto impl = reinterpret_cast<TestRadosClient*>(p);
+ if (impl) {
+ impl->get();
+ rados.client = reinterpret_cast<RadosClient*>(impl);
+ }
+}
+
+AioCompletion *Rados::aio_create_completion(void *cb_arg,
+ callback_t cb_complete) {
+ AioCompletionImpl *c;
+ int r = rados_aio_create_completion2(cb_arg, cb_complete,
+ reinterpret_cast<void**>(&c));
+ ceph_assert(r == 0);
+ return new AioCompletion(c);
+}
+
+int Rados::aio_watch_flush(AioCompletion* c) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->aio_watch_flush(c->pc);
+}
+
+int Rados::blocklist_add(const std::string& client_address,
+ uint32_t expire_seconds) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->blocklist_add(client_address, expire_seconds);
+}
+
+config_t Rados::cct() {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return reinterpret_cast<config_t>(impl->cct());
+}
+
+int Rados::cluster_fsid(std::string* fsid) {
+ *fsid = "00000000-1111-2222-3333-444444444444";
+ return 0;
+}
+
+int Rados::conf_set(const char *option, const char *value) {
+ return rados_conf_set(reinterpret_cast<rados_t>(client), option, value);
+}
+
+int Rados::conf_get(const char *option, std::string &val) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ CephContext *cct = impl->cct();
+
+ char *str = NULL;
+ int ret = cct->_conf.get_val(option, &str, -1);
+ if (ret != 0) {
+ free(str);
+ return ret;
+ }
+
+ val = str;
+ free(str);
+ return 0;
+}
+
+int Rados::conf_parse_env(const char *env) const {
+ return rados_conf_parse_env(reinterpret_cast<rados_t>(client), env);
+}
+
+int Rados::conf_read_file(const char * const path) const {
+ return rados_conf_read_file(reinterpret_cast<rados_t>(client), path);
+}
+
+int Rados::connect() {
+ return rados_connect(reinterpret_cast<rados_t>(client));
+}
+
+uint64_t Rados::get_instance_id() {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->get_instance_id();
+}
+
+int Rados::get_min_compatible_osd(int8_t* require_osd_release) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->get_min_compatible_osd(require_osd_release);
+}
+
+int Rados::get_min_compatible_client(int8_t* min_compat_client,
+ int8_t* require_min_compat_client) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->get_min_compatible_client(min_compat_client,
+ require_min_compat_client);
+}
+
+int Rados::init(const char * const id) {
+ return rados_create(reinterpret_cast<rados_t *>(&client), id);
+}
+
+int Rados::init_with_context(config_t cct_) {
+ return rados_create_with_context(reinterpret_cast<rados_t *>(&client), cct_);
+}
+
+int Rados::ioctx_create(const char *name, IoCtx &io) {
+ rados_ioctx_t p;
+ int ret = rados_ioctx_create(reinterpret_cast<rados_t>(client), name, &p);
+ if (ret) {
+ return ret;
+ }
+
+ io.close();
+ io.io_ctx_impl = reinterpret_cast<IoCtxImpl*>(p);
+ return 0;
+}
+
+int Rados::ioctx_create2(int64_t pool_id, IoCtx &io)
+{
+ rados_ioctx_t p;
+ int ret = rados_ioctx_create2(reinterpret_cast<rados_t>(client), pool_id, &p);
+ if (ret) {
+ return ret;
+ }
+
+ io.close();
+ io.io_ctx_impl = reinterpret_cast<IoCtxImpl*>(p);
+ return 0;
+}
+
+int Rados::mon_command(std::string cmd, const bufferlist& inbl,
+ bufferlist *outbl, std::string *outs) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+
+ std::vector<std::string> cmds;
+ cmds.push_back(cmd);
+ return impl->mon_command(cmds, inbl, outbl, outs);
+}
+
+int Rados::service_daemon_register(const std::string& service,
+ const std::string& name,
+ const std::map<std::string,std::string>& metadata) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->service_daemon_register(service, name, metadata);
+}
+
+int Rados::service_daemon_update_status(std::map<std::string,std::string>&& status) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->service_daemon_update_status(std::move(status));
+}
+
+int Rados::pool_create(const char *name) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->pool_create(name);
+}
+
+int Rados::pool_delete(const char *name) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->pool_delete(name);
+}
+
+int Rados::pool_get_base_tier(int64_t pool, int64_t* base_tier) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->pool_get_base_tier(pool, base_tier);
+}
+
+int Rados::pool_list(std::list<std::string>& v) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ std::list<std::pair<int64_t, std::string> > pools;
+ int r = impl->pool_list(pools);
+ if (r < 0) {
+ return r;
+ }
+
+ v.clear();
+ for (std::list<std::pair<int64_t, std::string> >::iterator it = pools.begin();
+ it != pools.end(); ++it) {
+ v.push_back(it->second);
+ }
+ return 0;
+}
+
+int Rados::pool_list2(std::list<std::pair<int64_t, std::string> >& v)
+{
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->pool_list(v);
+}
+
+int64_t Rados::pool_lookup(const char *name) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->pool_lookup(name);
+}
+
+int Rados::pool_reverse_lookup(int64_t id, std::string *name) {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->pool_reverse_lookup(id, name);
+}
+
+void Rados::shutdown() {
+ if (client == NULL) {
+ return;
+ }
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ impl->put();
+ client = NULL;
+}
+
+void Rados::test_blocklist_self(bool set) {
+}
+
+int Rados::wait_for_latest_osdmap() {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->wait_for_latest_osdmap();
+}
+
+int Rados::watch_flush() {
+ TestRadosClient *impl = reinterpret_cast<TestRadosClient*>(client);
+ return impl->watch_flush();
+}
+
+WatchCtx::~WatchCtx() {
+}
+
+WatchCtx2::~WatchCtx2() {
+}
+
+} // namespace librados
+
+int cls_cxx_create(cls_method_context_t hctx, bool exclusive) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->create(ctx->oid, exclusive, ctx->snapc);
+}
+
+int cls_cxx_remove(cls_method_context_t hctx) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->remove(ctx->oid, ctx->io_ctx_impl->get_snap_context());
+}
+
+int cls_get_request_origin(cls_method_context_t hctx, entity_inst_t *origin) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+
+ librados::TestRadosClient *rados_client =
+ ctx->io_ctx_impl->get_rados_client();
+
+ struct sockaddr_in sin;
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = 0;
+ inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr);
+
+ entity_addr_t entity_addr(entity_addr_t::TYPE_DEFAULT,
+ rados_client->get_nonce());
+ entity_addr.in4_addr() = sin;
+
+ *origin = entity_inst_t(
+ entity_name_t::CLIENT(rados_client->get_instance_id()),
+ entity_addr);
+ return 0;
+}
+
+int cls_cxx_getxattr(cls_method_context_t hctx, const char *name,
+ bufferlist *outbl) {
+ std::map<string, bufferlist> attrs;
+ int r = cls_cxx_getxattrs(hctx, &attrs);
+ if (r < 0) {
+ return r;
+ }
+
+ std::map<string, bufferlist>::iterator it = attrs.find(name);
+ if (it == attrs.end()) {
+ return -ENODATA;
+ }
+ *outbl = it->second;
+ return 0;
+}
+
+int cls_cxx_getxattrs(cls_method_context_t hctx, std::map<string, bufferlist> *attrset) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->xattr_get(ctx->oid, attrset);
+}
+
+int cls_cxx_map_get_keys(cls_method_context_t hctx, const string &start_obj,
+ uint64_t max_to_get, std::set<string> *keys, bool *more) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+
+ keys->clear();
+ std::map<string, bufferlist> vals;
+ int r = ctx->io_ctx_impl->omap_get_vals2(ctx->oid, start_obj, "", max_to_get,
+ &vals, more);
+ if (r < 0) {
+ return r;
+ }
+
+ for (std::map<string, bufferlist>::iterator it = vals.begin();
+ it != vals.end(); ++it) {
+ keys->insert(it->first);
+ }
+ return keys->size();
+}
+
+int cls_cxx_map_get_val(cls_method_context_t hctx, const string &key,
+ bufferlist *outbl) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+
+ std::map<string, bufferlist> vals;
+ int r = ctx->io_ctx_impl->omap_get_vals(ctx->oid, "", key, 1024, &vals);
+ if (r < 0) {
+ return r;
+ }
+
+ std::map<string, bufferlist>::iterator it = vals.find(key);
+ if (it == vals.end()) {
+ return -ENOENT;
+ }
+
+ *outbl = it->second;
+ return 0;
+}
+
+int cls_cxx_map_get_vals(cls_method_context_t hctx, const string &start_obj,
+ const string &filter_prefix, uint64_t max_to_get,
+ std::map<string, bufferlist> *vals, bool *more) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ int r = ctx->io_ctx_impl->omap_get_vals2(ctx->oid, start_obj, filter_prefix,
+ max_to_get, vals, more);
+ if (r < 0) {
+ return r;
+ }
+ return vals->size();
+}
+
+int cls_cxx_map_remove_key(cls_method_context_t hctx, const string &key) {
+ std::set<std::string> keys;
+ keys.insert(key);
+
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->omap_rm_keys(ctx->oid, keys);
+}
+
+int cls_cxx_map_set_val(cls_method_context_t hctx, const string &key,
+ bufferlist *inbl) {
+ std::map<std::string, bufferlist> m;
+ m[key] = *inbl;
+ return cls_cxx_map_set_vals(hctx, &m);
+}
+
+int cls_cxx_map_set_vals(cls_method_context_t hctx,
+ const std::map<string, bufferlist> *map) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->omap_set(ctx->oid, *map);
+}
+
+int cls_cxx_read(cls_method_context_t hctx, int ofs, int len,
+ bufferlist *outbl) {
+ return cls_cxx_read2(hctx, ofs, len, outbl, 0);
+}
+
+int cls_cxx_read2(cls_method_context_t hctx, int ofs, int len,
+ bufferlist *outbl, uint32_t op_flags) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->read(
+ ctx->oid, len, ofs, outbl, ctx->snap_id, nullptr);
+}
+
+int cls_cxx_setxattr(cls_method_context_t hctx, const char *name,
+ bufferlist *inbl) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->xattr_set(ctx->oid, name, *inbl);
+}
+
+int cls_cxx_stat(cls_method_context_t hctx, uint64_t *size, time_t *mtime) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->stat(ctx->oid, size, mtime);
+}
+
+int cls_cxx_write(cls_method_context_t hctx, int ofs, int len,
+ bufferlist *inbl) {
+ return cls_cxx_write2(hctx, ofs, len, inbl, 0);
+}
+
+int cls_cxx_write2(cls_method_context_t hctx, int ofs, int len,
+ bufferlist *inbl, uint32_t op_flags) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->write(ctx->oid, *inbl, len, ofs, ctx->snapc);
+}
+
+int cls_cxx_write_full(cls_method_context_t hctx, bufferlist *inbl) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->write_full(ctx->oid, *inbl, ctx->snapc);
+}
+
+int cls_cxx_replace(cls_method_context_t hctx, int ofs, int len,
+ bufferlist *inbl) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ int r = ctx->io_ctx_impl->truncate(ctx->oid, 0, ctx->snapc);
+ if (r < 0) {
+ return r;
+ }
+ return ctx->io_ctx_impl->write(ctx->oid, *inbl, len, ofs, ctx->snapc);
+}
+
+int cls_cxx_truncate(cls_method_context_t hctx, int ofs) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->truncate(ctx->oid, ofs, ctx->snapc);
+}
+
+int cls_cxx_write_zero(cls_method_context_t hctx, int ofs, int len) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ return ctx->io_ctx_impl->zero(ctx->oid, len, ofs, ctx->snapc);
+}
+
+int cls_cxx_list_watchers(cls_method_context_t hctx,
+ obj_list_watch_response_t *watchers) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+
+ std::list<obj_watch_t> obj_watchers;
+ int r = ctx->io_ctx_impl->list_watchers(ctx->oid, &obj_watchers);
+ if (r < 0) {
+ return r;
+ }
+
+ for (auto &w : obj_watchers) {
+ watch_item_t watcher;
+ watcher.name = entity_name_t::CLIENT(w.watcher_id);
+ watcher.cookie = w.cookie;
+ watcher.timeout_seconds = w.timeout_seconds;
+ watcher.addr.parse(w.addr);
+ watchers->entries.push_back(watcher);
+ }
+
+ return 0;
+}
+
+uint64_t cls_get_features(cls_method_context_t hctx) {
+ return CEPH_FEATURES_SUPPORTED_DEFAULT;
+}
+
+uint64_t cls_get_client_features(cls_method_context_t hctx) {
+ return CEPH_FEATURES_SUPPORTED_DEFAULT;
+}
+
+int cls_get_snapset_seq(cls_method_context_t hctx, uint64_t *snap_seq) {
+ librados::TestClassHandler::MethodContext *ctx =
+ reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx);
+ librados::snap_set_t snapset;
+ int r = ctx->io_ctx_impl->list_snaps(ctx->oid, &snapset);
+ if (r < 0) {
+ return r;
+ }
+
+ *snap_seq = snapset.seq;
+ return 0;
+}
+
+int cls_log(int level, const char *format, ...) {
+ int size = 256;
+ va_list ap;
+ while (1) {
+ char buf[size];
+ va_start(ap, format);
+ int n = vsnprintf(buf, size, format, ap);
+ va_end(ap);
+ if ((n > -1 && n < size) || size > 8196) {
+ dout(ceph::dout::need_dynamic(level)) << buf << dendl;
+ return n;
+ }
+ size *= 2;
+ }
+ return 0;
+}
+
+int cls_register(const char *name, cls_handle_t *handle) {
+ librados::TestClassHandler *cls = librados_test_stub::get_class_handler();
+ return cls->create(name, handle);
+}
+
+int cls_register_cxx_method(cls_handle_t hclass, const char *method,
+ int flags,
+ cls_method_cxx_call_t class_call,
+ cls_method_handle_t *handle) {
+ librados::TestClassHandler *cls = librados_test_stub::get_class_handler();
+ return cls->create_method(hclass, method, class_call, handle);
+}
+
+int cls_register_cxx_filter(cls_handle_t hclass,
+ const std::string &filter_name,
+ cls_cxx_filter_factory_t fn,
+ cls_filter_handle_t *)
+{
+ librados::TestClassHandler *cls = librados_test_stub::get_class_handler();
+ return cls->create_filter(hclass, filter_name, fn);
+}
+
+ceph_release_t cls_get_required_osd_release(cls_handle_t hclass) {
+ return ceph_release_t::nautilus;
+}
+
+ceph_release_t cls_get_min_compatible_client(cls_handle_t hclass) {
+ return ceph_release_t::nautilus;
+}
+
+// stubs to silence TestClassHandler::open_class()
+PGLSFilter::~PGLSFilter()
+{}
+
+int cls_gen_rand_base64(char *, int) {
+ return -ENOTSUP;
+}
+
+int cls_cxx_chunk_write_and_set(cls_method_handle_t, int,
+ int, bufferlist *,
+ uint32_t, bufferlist *, int) {
+ return -ENOTSUP;
+}
+
+int cls_cxx_map_read_header(cls_method_handle_t, bufferlist *) {
+ return -ENOTSUP;
+}
+
+uint64_t cls_get_osd_min_alloc_size(cls_method_context_t hctx) {
+ return 0;
+}
+
+uint64_t cls_get_pool_stripe_width(cls_method_context_t hctx) {
+ return 0;
+}
diff --git a/src/test/librados_test_stub/LibradosTestStub.h b/src/test/librados_test_stub/LibradosTestStub.h
new file mode 100644
index 000000000..5d335f488
--- /dev/null
+++ b/src/test/librados_test_stub/LibradosTestStub.h
@@ -0,0 +1,42 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef LIBRADOS_TEST_STUB_H
+#define LIBRADOS_TEST_STUB_H
+
+#include "include/rados/librados_fwd.hpp"
+#include <boost/shared_ptr.hpp>
+
+namespace neorados {
+struct IOContext;
+struct RADOS;
+} // namespace neorados
+
+namespace librados {
+
+class MockTestMemIoCtxImpl;
+class MockTestMemRadosClient;
+class TestCluster;
+class TestClassHandler;
+
+MockTestMemIoCtxImpl &get_mock_io_ctx(IoCtx &ioctx);
+MockTestMemIoCtxImpl &get_mock_io_ctx(neorados::RADOS& rados,
+ neorados::IOContext& io_context);
+
+MockTestMemRadosClient &get_mock_rados_client(neorados::RADOS& rados);
+
+} // namespace librados
+
+namespace librados_test_stub {
+
+typedef boost::shared_ptr<librados::TestCluster> TestClusterRef;
+
+void set_cluster(TestClusterRef cluster);
+TestClusterRef get_cluster();
+
+librados::TestClassHandler* get_class_handler();
+
+} // namespace librados_test_stub
+
+
+#endif // LIBRADOS_TEST_STUB_H
diff --git a/src/test/librados_test_stub/MockTestMemCluster.h b/src/test/librados_test_stub/MockTestMemCluster.h
new file mode 100644
index 000000000..4a6da0cc0
--- /dev/null
+++ b/src/test/librados_test_stub/MockTestMemCluster.h
@@ -0,0 +1,36 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef LIBRADOS_MOCK_TEST_MEM_CLUSTER_H
+#define LIBRADOS_MOCK_TEST_MEM_CLUSTER_H
+
+#include "include/common_fwd.h"
+#include "test/librados_test_stub/TestMemCluster.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "gmock/gmock.h"
+
+
+namespace librados {
+
+class TestRadosClient;
+
+class MockTestMemCluster : public TestMemCluster {
+public:
+ MockTestMemCluster() {
+ default_to_dispatch();
+ }
+
+ MOCK_METHOD1(create_rados_client, TestRadosClient*(CephContext*));
+ MockTestMemRadosClient* do_create_rados_client(CephContext *cct) {
+ return new ::testing::NiceMock<MockTestMemRadosClient>(cct, this);
+ }
+
+ void default_to_dispatch() {
+ using namespace ::testing;
+ ON_CALL(*this, create_rados_client(_)).WillByDefault(Invoke(this, &MockTestMemCluster::do_create_rados_client));
+ }
+};
+
+} // namespace librados
+
+#endif // LIBRADOS_MOCK_TEST_MEM_CLUSTER_H
diff --git a/src/test/librados_test_stub/MockTestMemIoCtxImpl.h b/src/test/librados_test_stub/MockTestMemIoCtxImpl.h
new file mode 100644
index 000000000..aed431c11
--- /dev/null
+++ b/src/test/librados_test_stub/MockTestMemIoCtxImpl.h
@@ -0,0 +1,252 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef LIBRADOS_TEST_STUB_MOCK_TEST_MEM_IO_CTX_IMPL_H
+#define LIBRADOS_TEST_STUB_MOCK_TEST_MEM_IO_CTX_IMPL_H
+
+#include "test/librados_test_stub/TestMemIoCtxImpl.h"
+#include "test/librados_test_stub/TestMemCluster.h"
+#include "gmock/gmock.h"
+
+namespace librados {
+
+class MockTestMemRadosClient;
+
+class MockTestMemIoCtxImpl : public TestMemIoCtxImpl {
+public:
+ MockTestMemIoCtxImpl(MockTestMemRadosClient *mock_client,
+ TestMemRadosClient *client, int64_t pool_id,
+ const std::string& pool_name,
+ TestMemCluster::Pool *pool)
+ : TestMemIoCtxImpl(client, pool_id, pool_name, pool),
+ m_mock_client(mock_client), m_client(client) {
+ default_to_parent();
+ }
+
+ MockTestMemRadosClient *get_mock_rados_client() {
+ return m_mock_client;
+ }
+
+ MOCK_METHOD0(clone, TestIoCtxImpl*());
+ TestIoCtxImpl *do_clone() {
+ TestIoCtxImpl *io_ctx_impl = new ::testing::NiceMock<MockTestMemIoCtxImpl>(
+ m_mock_client, m_client, get_pool_id(), get_pool_name(), get_pool());
+ io_ctx_impl->set_snap_read(get_snap_read());
+ io_ctx_impl->set_snap_context(get_snap_context());
+ return io_ctx_impl;
+ }
+
+ MOCK_METHOD5(aio_notify, void(const std::string& o, AioCompletionImpl *c,
+ bufferlist& bl, uint64_t timeout_ms,
+ bufferlist *pbl));
+ void do_aio_notify(const std::string& o, AioCompletionImpl *c, bufferlist& bl,
+ uint64_t timeout_ms, bufferlist *pbl) {
+ return TestMemIoCtxImpl::aio_notify(o, c, bl, timeout_ms, pbl);
+ }
+
+ MOCK_METHOD6(aio_operate, int(const std::string&, TestObjectOperationImpl&,
+ AioCompletionImpl*, SnapContext*,
+ const ceph::real_time*, int));
+ int do_aio_operate(const std::string& o, TestObjectOperationImpl& ops,
+ AioCompletionImpl* c, SnapContext* snapc,
+ const ceph::real_time* pmtime, int flags) {
+ return TestMemIoCtxImpl::aio_operate(o, ops, c, snapc, pmtime, flags);
+ }
+
+ MOCK_METHOD4(aio_watch, int(const std::string& o, AioCompletionImpl *c,
+ uint64_t *handle, librados::WatchCtx2 *ctx));
+ int do_aio_watch(const std::string& o, AioCompletionImpl *c,
+ uint64_t *handle, librados::WatchCtx2 *ctx) {
+ return TestMemIoCtxImpl::aio_watch(o, c, handle, ctx);
+ }
+
+ MOCK_METHOD2(aio_unwatch, int(uint64_t handle, AioCompletionImpl *c));
+ int do_aio_unwatch(uint64_t handle, AioCompletionImpl *c) {
+ return TestMemIoCtxImpl::aio_unwatch(handle, c);
+ }
+
+ MOCK_METHOD2(assert_exists, int(const std::string &, uint64_t));
+ int do_assert_exists(const std::string &oid, uint64_t snap_id) {
+ return TestMemIoCtxImpl::assert_exists(oid, snap_id);
+ }
+
+ MOCK_METHOD2(assert_version, int(const std::string &, uint64_t));
+ int do_assert_version(const std::string &oid, uint64_t ver) {
+ return TestMemIoCtxImpl::assert_version(oid, ver);
+ }
+
+ MOCK_METHOD3(create, int(const std::string&, bool, const SnapContext &));
+ int do_create(const std::string& oid, bool exclusive,
+ const SnapContext &snapc) {
+ return TestMemIoCtxImpl::create(oid, exclusive, snapc);
+ }
+
+ MOCK_METHOD4(cmpext, int(const std::string&, uint64_t, bufferlist&,
+ uint64_t snap_id));
+ int do_cmpext(const std::string& oid, uint64_t off, bufferlist& cmp_bl,
+ uint64_t snap_id) {
+ return TestMemIoCtxImpl::cmpext(oid, off, cmp_bl, snap_id);
+ }
+
+ MOCK_METHOD8(exec, int(const std::string& oid,
+ TestClassHandler *handler,
+ const char *cls,
+ const char *method,
+ bufferlist& inbl,
+ bufferlist* outbl,
+ uint64_t snap_id,
+ const SnapContext &snapc));
+ int do_exec(const std::string& oid, TestClassHandler *handler,
+ const char *cls, const char *method, bufferlist& inbl,
+ bufferlist* outbl, uint64_t snap_id, const SnapContext &snapc) {
+ return TestMemIoCtxImpl::exec(oid, handler, cls, method, inbl, outbl,
+ snap_id, snapc);
+ }
+
+ MOCK_CONST_METHOD0(get_instance_id, uint64_t());
+ uint64_t do_get_instance_id() const {
+ return TestMemIoCtxImpl::get_instance_id();
+ }
+
+ MOCK_METHOD2(list_snaps, int(const std::string& o, snap_set_t *out_snaps));
+ int do_list_snaps(const std::string& o, snap_set_t *out_snaps) {
+ return TestMemIoCtxImpl::list_snaps(o, out_snaps);
+ }
+
+ MOCK_METHOD2(list_watchers, int(const std::string& o,
+ std::list<obj_watch_t> *out_watchers));
+ int do_list_watchers(const std::string& o,
+ std::list<obj_watch_t> *out_watchers) {
+ return TestMemIoCtxImpl::list_watchers(o, out_watchers);
+ }
+
+ MOCK_METHOD4(notify, int(const std::string& o, bufferlist& bl,
+ uint64_t timeout_ms, bufferlist *pbl));
+ int do_notify(const std::string& o, bufferlist& bl,
+ uint64_t timeout_ms, bufferlist *pbl) {
+ return TestMemIoCtxImpl::notify(o, bl, timeout_ms, pbl);
+ }
+
+ MOCK_METHOD1(set_snap_read, void(snap_t));
+ void do_set_snap_read(snap_t snap_id) {
+ return TestMemIoCtxImpl::set_snap_read(snap_id);
+ }
+ MOCK_METHOD6(sparse_read, int(const std::string& oid,
+ uint64_t off,
+ uint64_t len,
+ std::map<uint64_t, uint64_t> *m,
+ bufferlist *bl, uint64_t));
+ int do_sparse_read(const std::string& oid, uint64_t off, size_t len,
+ std::map<uint64_t, uint64_t> *m, bufferlist *bl,
+ uint64_t snap_id) {
+ return TestMemIoCtxImpl::sparse_read(oid, off, len, m, bl, snap_id);
+ }
+
+ MOCK_METHOD6(read, int(const std::string& oid,
+ size_t len,
+ uint64_t off,
+ bufferlist *bl, uint64_t snap_id, uint64_t* objver));
+ int do_read(const std::string& oid, size_t len, uint64_t off,
+ bufferlist *bl, uint64_t snap_id, uint64_t* objver) {
+ return TestMemIoCtxImpl::read(oid, len, off, bl, snap_id, objver);
+ }
+
+ MOCK_METHOD2(remove, int(const std::string& oid, const SnapContext &snapc));
+ int do_remove(const std::string& oid, const SnapContext &snapc) {
+ return TestMemIoCtxImpl::remove(oid, snapc);
+ }
+
+ MOCK_METHOD1(selfmanaged_snap_create, int(uint64_t *snap_id));
+ int do_selfmanaged_snap_create(uint64_t *snap_id) {
+ return TestMemIoCtxImpl::selfmanaged_snap_create(snap_id);
+ }
+
+ MOCK_METHOD1(selfmanaged_snap_remove, int(uint64_t snap_id));
+ int do_selfmanaged_snap_remove(uint64_t snap_id) {
+ return TestMemIoCtxImpl::selfmanaged_snap_remove(snap_id);
+ }
+
+ MOCK_METHOD2(selfmanaged_snap_rollback, int(const std::string& oid,
+ uint64_t snap_id));
+ int do_selfmanaged_snap_rollback(const std::string& oid, uint64_t snap_id) {
+ return TestMemIoCtxImpl::selfmanaged_snap_rollback(oid, snap_id);
+ }
+
+ MOCK_METHOD3(truncate, int(const std::string& oid,
+ uint64_t size,
+ const SnapContext &snapc));
+ int do_truncate(const std::string& oid, uint64_t size,
+ const SnapContext &snapc) {
+ return TestMemIoCtxImpl::truncate(oid, size, snapc);
+ }
+
+ MOCK_METHOD5(write, int(const std::string& oid, bufferlist& bl, size_t len,
+ uint64_t off, const SnapContext &snapc));
+ int do_write(const std::string& oid, bufferlist& bl, size_t len, uint64_t off,
+ const SnapContext &snapc) {
+ return TestMemIoCtxImpl::write(oid, bl, len, off, snapc);
+ }
+
+ MOCK_METHOD3(write_full, int(const std::string& oid,
+ bufferlist& bl,
+ const SnapContext &snapc));
+ int do_write_full(const std::string& oid, bufferlist& bl,
+ const SnapContext &snapc) {
+ return TestMemIoCtxImpl::write_full(oid, bl, snapc);
+ }
+
+
+ MOCK_METHOD5(writesame, int(const std::string& oid, bufferlist& bl,
+ size_t len, uint64_t off,
+ const SnapContext &snapc));
+ int do_writesame(const std::string& oid, bufferlist& bl, size_t len,
+ uint64_t off, const SnapContext &snapc) {
+ return TestMemIoCtxImpl::writesame(oid, bl, len, off, snapc);
+ }
+
+ MOCK_METHOD4(zero, int(const std::string& oid, uint64_t offset,
+ uint64_t length, const SnapContext &snapc));
+ int do_zero(const std::string& oid, uint64_t offset,
+ uint64_t length, const SnapContext &snapc) {
+ return TestMemIoCtxImpl::zero(oid, offset, length, snapc);
+ }
+
+ void default_to_parent() {
+ using namespace ::testing;
+
+ ON_CALL(*this, clone()).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_clone));
+ ON_CALL(*this, aio_notify(_, _, _, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_aio_notify));
+ ON_CALL(*this, aio_operate(_, _, _, _, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_aio_operate));
+ ON_CALL(*this, aio_watch(_, _, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_aio_watch));
+ ON_CALL(*this, aio_unwatch(_, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_aio_unwatch));
+ ON_CALL(*this, assert_exists(_, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_assert_exists));
+ ON_CALL(*this, assert_version(_, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_assert_version));
+ ON_CALL(*this, create(_, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_create));
+ ON_CALL(*this, cmpext(_, _, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_cmpext));
+ ON_CALL(*this, exec(_, _, _, _, _, _, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_exec));
+ ON_CALL(*this, get_instance_id()).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_get_instance_id));
+ ON_CALL(*this, list_snaps(_, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_list_snaps));
+ ON_CALL(*this, list_watchers(_, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_list_watchers));
+ ON_CALL(*this, notify(_, _, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_notify));
+ ON_CALL(*this, read(_, _, _, _, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_read));
+ ON_CALL(*this, set_snap_read(_)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_set_snap_read));
+ ON_CALL(*this, sparse_read(_, _, _, _, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_sparse_read));
+ ON_CALL(*this, remove(_, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_remove));
+ ON_CALL(*this, selfmanaged_snap_create(_)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_selfmanaged_snap_create));
+ ON_CALL(*this, selfmanaged_snap_remove(_)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_selfmanaged_snap_remove));
+ ON_CALL(*this, selfmanaged_snap_rollback(_, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_selfmanaged_snap_rollback));
+ ON_CALL(*this, truncate(_,_,_)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_truncate));
+ ON_CALL(*this, write(_, _, _, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_write));
+ ON_CALL(*this, write_full(_, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_write_full));
+ ON_CALL(*this, writesame(_, _, _, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_writesame));
+ ON_CALL(*this, zero(_,_,_,_)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_zero));
+ }
+
+private:
+ MockTestMemRadosClient *m_mock_client;
+ TestMemRadosClient *m_client;
+};
+
+} // namespace librados
+
+#endif // LIBRADOS_TEST_STUB_MOCK_TEST_MEM_IO_CTX_IMPL_H
diff --git a/src/test/librados_test_stub/MockTestMemRadosClient.h b/src/test/librados_test_stub/MockTestMemRadosClient.h
new file mode 100644
index 000000000..65a1ac82e
--- /dev/null
+++ b/src/test/librados_test_stub/MockTestMemRadosClient.h
@@ -0,0 +1,103 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef LIBRADOS_TEST_STUB_MOCK_TEST_MEM_RADOS_CLIENT_H
+#define LIBRADOS_TEST_STUB_MOCK_TEST_MEM_RADOS_CLIENT_H
+
+#include "test/librados_test_stub/TestMemRadosClient.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "gmock/gmock.h"
+
+namespace librados {
+
+class TestMemCluster;
+
+class MockTestMemRadosClient : public TestMemRadosClient {
+public:
+ MockTestMemRadosClient(CephContext *cct, TestMemCluster *test_mem_cluster)
+ : TestMemRadosClient(cct, test_mem_cluster) {
+ default_to_dispatch();
+ }
+
+ MOCK_METHOD0(connect, int());
+ int do_connect() {
+ return TestMemRadosClient::connect();
+ }
+
+ MOCK_METHOD2(create_ioctx, TestIoCtxImpl *(int64_t pool_id,
+ const std::string &pool_name));
+ MockTestMemIoCtxImpl* do_create_ioctx(int64_t pool_id,
+ const std::string &pool_name) {
+ return new ::testing::NiceMock<MockTestMemIoCtxImpl>(
+ this, this, pool_id, pool_name,
+ get_mem_cluster()->get_pool(pool_name));
+ }
+
+ MOCK_METHOD2(blocklist_add, int(const std::string& client_address,
+ uint32_t expire_seconds));
+ int do_blocklist_add(const std::string& client_address,
+ uint32_t expire_seconds) {
+ return TestMemRadosClient::blocklist_add(client_address, expire_seconds);
+ }
+
+ MOCK_METHOD1(get_min_compatible_osd, int(int8_t*));
+ int do_get_min_compatible_osd(int8_t* require_osd_release) {
+ return TestMemRadosClient::get_min_compatible_osd(require_osd_release);
+ }
+
+ MOCK_METHOD2(get_min_compatible_client, int(int8_t*, int8_t*));
+ int do_get_min_compatible_client(int8_t* min_compat_client,
+ int8_t* require_min_compat_client) {
+ return TestMemRadosClient::get_min_compatible_client(
+ min_compat_client, require_min_compat_client);
+ }
+
+ MOCK_METHOD3(service_daemon_register,
+ int(const std::string&,
+ const std::string&,
+ const std::map<std::string,std::string>&));
+ int do_service_daemon_register(const std::string& service,
+ const std::string& name,
+ const std::map<std::string,std::string>& metadata) {
+ return TestMemRadosClient::service_daemon_register(service, name, metadata);
+ }
+
+ // workaround of https://github.com/google/googletest/issues/1155
+ MOCK_METHOD1(service_daemon_update_status_r,
+ int(const std::map<std::string,std::string>&));
+ int do_service_daemon_update_status_r(const std::map<std::string,std::string>& status) {
+ auto s = status;
+ return TestMemRadosClient::service_daemon_update_status(std::move(s));
+ }
+
+ MOCK_METHOD4(mon_command, int(const std::vector<std::string>&,
+ const bufferlist&, bufferlist*, std::string*));
+ int do_mon_command(const std::vector<std::string>& cmd,
+ const bufferlist &inbl, bufferlist *outbl,
+ std::string *outs) {
+ return mon_command(cmd, inbl, outbl, outs);
+ }
+
+ MOCK_METHOD0(wait_for_latest_osd_map, int());
+ int do_wait_for_latest_osd_map() {
+ return wait_for_latest_osd_map();
+ }
+
+ void default_to_dispatch() {
+ using namespace ::testing;
+
+ ON_CALL(*this, connect()).WillByDefault(Invoke(this, &MockTestMemRadosClient::do_connect));
+ ON_CALL(*this, create_ioctx(_, _)).WillByDefault(Invoke(this, &MockTestMemRadosClient::do_create_ioctx));
+ ON_CALL(*this, blocklist_add(_, _)).WillByDefault(Invoke(this, &MockTestMemRadosClient::do_blocklist_add));
+ ON_CALL(*this, get_min_compatible_osd(_)).WillByDefault(Invoke(this, &MockTestMemRadosClient::do_get_min_compatible_osd));
+ ON_CALL(*this, get_min_compatible_client(_, _)).WillByDefault(Invoke(this, &MockTestMemRadosClient::do_get_min_compatible_client));
+ ON_CALL(*this, service_daemon_register(_, _, _)).WillByDefault(Invoke(this, &MockTestMemRadosClient::do_service_daemon_register));
+ ON_CALL(*this, service_daemon_update_status_r(_)).WillByDefault(Invoke(this, &MockTestMemRadosClient::do_service_daemon_update_status_r));
+ ON_CALL(*this, mon_command(_, _, _, _)).WillByDefault(Invoke(this, &MockTestMemRadosClient::do_mon_command));
+ ON_CALL(*this, wait_for_latest_osd_map()).WillByDefault(Invoke(this, &MockTestMemRadosClient::do_wait_for_latest_osd_map));
+ }
+};
+
+} // namespace librados
+
+#endif // LIBRADOS_TEST_STUB_MOCK_TEST_MEM_RADOS_CLIENT_H
diff --git a/src/test/librados_test_stub/NeoradosTestStub.cc b/src/test/librados_test_stub/NeoradosTestStub.cc
new file mode 100644
index 000000000..0de2cd902
--- /dev/null
+++ b/src/test/librados_test_stub/NeoradosTestStub.cc
@@ -0,0 +1,601 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/neorados/RADOS.hpp"
+#include "include/rados/librados.hpp"
+#include "common/ceph_mutex.h"
+#include "common/hobject.h"
+#include "librados/AioCompletionImpl.h"
+#include "mon/error_code.h"
+#include "osd/error_code.h"
+#include "osd/osd_types.h"
+#include "osdc/error_code.h"
+#include "test/librados_test_stub/LibradosTestStub.h"
+#include "test/librados_test_stub/TestClassHandler.h"
+#include "test/librados_test_stub/TestIoCtxImpl.h"
+#include "test/librados_test_stub/TestRadosClient.h"
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <functional>
+#include <boost/system/system_error.hpp>
+
+namespace bs = boost::system;
+using namespace std::literals;
+using namespace std::placeholders;
+
+namespace neorados {
+namespace detail {
+
+class Client {
+public:
+ ceph::mutex mutex = ceph::make_mutex("NeoradosTestStub::Client");
+
+ librados::TestRadosClient* test_rados_client;
+ boost::asio::io_context& io_context;
+
+ std::map<std::pair<int64_t, std::string>, librados::TestIoCtxImpl*> io_ctxs;
+
+ Client(librados::TestRadosClient* test_rados_client)
+ : test_rados_client(test_rados_client),
+ io_context(test_rados_client->get_io_context()) {
+ }
+
+ ~Client() {
+ for (auto& io_ctx : io_ctxs) {
+ io_ctx.second->put();
+ }
+ }
+
+ librados::TestIoCtxImpl* get_io_ctx(const IOContext& ioc) {
+ int64_t pool_id = ioc.pool();
+ std::string ns = std::string{ioc.ns()};
+
+ auto lock = std::scoped_lock{mutex};
+ auto key = make_pair(pool_id, ns);
+ auto it = io_ctxs.find(key);
+ if (it != io_ctxs.end()) {
+ return it->second;
+ }
+
+ std::list<std::pair<int64_t, std::string>> pools;
+ int r = test_rados_client->pool_list(pools);
+ if (r < 0) {
+ return nullptr;
+ }
+
+ for (auto& pool : pools) {
+ if (pool.first == pool_id) {
+ auto io_ctx = test_rados_client->create_ioctx(pool_id, pool.second);
+ io_ctx->set_namespace(ns);
+ io_ctxs[key] = io_ctx;
+ return io_ctx;
+ }
+ }
+ return nullptr;
+ }
+};
+
+} // namespace detail
+
+namespace {
+
+struct CompletionPayload {
+ std::unique_ptr<Op::Completion> c;
+};
+
+void completion_callback_adapter(rados_completion_t c, void *arg) {
+ auto impl = reinterpret_cast<librados::AioCompletionImpl *>(c);
+ auto r = impl->get_return_value();
+ impl->release();
+
+ auto payload = reinterpret_cast<CompletionPayload*>(arg);
+ payload->c->defer(std::move(payload->c),
+ (r < 0) ? bs::error_code(-r, osd_category()) :
+ bs::error_code());
+ delete payload;
+}
+
+librados::AioCompletionImpl* create_aio_completion(
+ std::unique_ptr<Op::Completion>&& c) {
+ auto payload = new CompletionPayload{std::move(c)};
+
+ auto impl = new librados::AioCompletionImpl();
+ impl->set_complete_callback(payload, completion_callback_adapter);
+
+ return impl;
+}
+
+int save_operation_size(int result, size_t* pval) {
+ if (pval != NULL) {
+ *pval = result;
+ }
+ return result;
+}
+
+int save_operation_ec(int result, boost::system::error_code* ec) {
+ if (ec != NULL) {
+ *ec = {std::abs(result), bs::system_category()};
+ }
+ return result;
+}
+
+} // anonymous namespace
+
+Object::Object() {
+ static_assert(impl_size >= sizeof(object_t));
+ new (&impl) object_t();
+}
+
+Object::Object(std::string&& s) {
+ static_assert(impl_size >= sizeof(object_t));
+ new (&impl) object_t(std::move(s));
+}
+
+Object::~Object() {
+ reinterpret_cast<object_t*>(&impl)->~object_t();
+}
+
+Object::operator std::string_view() const {
+ return std::string_view(reinterpret_cast<const object_t*>(&impl)->name);
+}
+
+struct IOContextImpl {
+ object_locator_t oloc;
+ snapid_t snap_seq = CEPH_NOSNAP;
+ SnapContext snapc;
+};
+
+IOContext::IOContext() {
+ static_assert(impl_size >= sizeof(IOContextImpl));
+ new (&impl) IOContextImpl();
+}
+
+IOContext::IOContext(const IOContext& rhs) {
+ static_assert(impl_size >= sizeof(IOContextImpl));
+ new (&impl) IOContextImpl(*reinterpret_cast<const IOContextImpl*>(&rhs.impl));
+}
+
+IOContext::IOContext(int64_t _pool, std::string&& _ns)
+ : IOContext() {
+ pool(_pool);
+ ns(std::move(_ns));
+}
+
+IOContext::~IOContext() {
+ reinterpret_cast<IOContextImpl*>(&impl)->~IOContextImpl();
+}
+
+std::int64_t IOContext::pool() const {
+ return reinterpret_cast<const IOContextImpl*>(&impl)->oloc.pool;
+}
+
+void IOContext::pool(std::int64_t _pool) {
+ reinterpret_cast<IOContextImpl*>(&impl)->oloc.pool = _pool;
+}
+
+std::string_view IOContext::ns() const {
+ return reinterpret_cast<const IOContextImpl*>(&impl)->oloc.nspace;
+}
+
+void IOContext::ns(std::string&& _ns) {
+ reinterpret_cast<IOContextImpl*>(&impl)->oloc.nspace = std::move(_ns);
+}
+
+std::optional<std::uint64_t> IOContext::read_snap() const {
+ auto& snap_seq = reinterpret_cast<const IOContextImpl*>(&impl)->snap_seq;
+ if (snap_seq == CEPH_NOSNAP)
+ return std::nullopt;
+ else
+ return snap_seq;
+}
+void IOContext::read_snap(std::optional<std::uint64_t> _snapid) {
+ auto& snap_seq = reinterpret_cast<IOContextImpl*>(&impl)->snap_seq;
+ snap_seq = _snapid.value_or(CEPH_NOSNAP);
+}
+
+std::optional<
+ std::pair<std::uint64_t,
+ std::vector<std::uint64_t>>> IOContext::write_snap_context() const {
+ auto& snapc = reinterpret_cast<const IOContextImpl*>(&impl)->snapc;
+ if (snapc.empty()) {
+ return std::nullopt;
+ } else {
+ std::vector<uint64_t> v(snapc.snaps.begin(), snapc.snaps.end());
+ return std::make_optional(std::make_pair(uint64_t(snapc.seq), v));
+ }
+}
+
+void IOContext::write_snap_context(
+ std::optional<std::pair<std::uint64_t, std::vector<std::uint64_t>>> _snapc) {
+ auto& snapc = reinterpret_cast<IOContextImpl*>(&impl)->snapc;
+ if (!_snapc) {
+ snapc.clear();
+ } else {
+ SnapContext n(_snapc->first, { _snapc->second.begin(), _snapc->second.end()});
+ if (!n.is_valid()) {
+ throw bs::system_error(EINVAL,
+ bs::system_category(),
+ "Invalid snap context.");
+ }
+
+ snapc = n;
+ }
+}
+
+void IOContext::full_try(bool _full_try) {
+ // no-op
+}
+
+bool operator ==(const IOContext& lhs, const IOContext& rhs) {
+ auto l = reinterpret_cast<const IOContextImpl*>(&lhs.impl);
+ auto r = reinterpret_cast<const IOContextImpl*>(&rhs.impl);
+ return (l->oloc == r->oloc &&
+ l->snap_seq == r->snap_seq &&
+ l->snapc.seq == r->snapc.seq &&
+ l->snapc.snaps == r->snapc.snaps);
+}
+
+bool operator !=(const IOContext& lhs, const IOContext& rhs) {
+ return !(lhs == rhs);
+}
+
+Op::Op() {
+ static_assert(Op::impl_size >= sizeof(librados::TestObjectOperationImpl*));
+ auto& o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ o = new librados::TestObjectOperationImpl();
+ o->get();
+}
+
+Op::~Op() {
+ auto& o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ if (o != nullptr) {
+ o->put();
+ o = nullptr;
+ }
+}
+
+void Op::assert_exists() {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ o->ops.push_back(std::bind(
+ &librados::TestIoCtxImpl::assert_exists, _1, _2, _4));
+}
+
+void Op::assert_version(uint64_t ver) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ o->ops.push_back(std::bind(
+ &librados::TestIoCtxImpl::assert_version, _1, _2, ver));
+}
+
+void Op::cmpext(uint64_t off, ceph::buffer::list&& cmp_bl, std::size_t* s) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ librados::ObjectOperationTestImpl op = std::bind(
+ &librados::TestIoCtxImpl::cmpext, _1, _2, off, cmp_bl, _4);
+ if (s != nullptr) {
+ op = std::bind(
+ save_operation_size, std::bind(op, _1, _2, _3, _4, _5, _6), s);
+ }
+ o->ops.push_back(op);
+}
+
+std::size_t Op::size() const {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl* const *>(&impl);
+ return o->ops.size();
+}
+
+void Op::set_fadvise_random() {
+ // no-op
+}
+
+void Op::set_fadvise_sequential() {
+ // no-op
+}
+
+void Op::set_fadvise_willneed() {
+ // no-op
+}
+
+void Op::set_fadvise_dontneed() {
+ // no-op
+}
+
+void Op::set_fadvise_nocache() {
+ // no-op
+}
+
+void Op::balance_reads() {
+ // no-op
+}
+
+void Op::localize_reads() {
+ // no-op
+}
+
+void Op::exec(std::string_view cls, std::string_view method,
+ const ceph::buffer::list& inbl,
+ ceph::buffer::list* out,
+ boost::system::error_code* ec) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+
+ auto cls_handler = librados_test_stub::get_class_handler();
+ librados::ObjectOperationTestImpl op =
+ [cls_handler, cls, method, inbl = const_cast<bufferlist&>(inbl), out]
+ (librados::TestIoCtxImpl* io_ctx, const std::string& oid, bufferlist* outbl,
+ uint64_t snap_id, const SnapContext& snapc, uint64_t*) mutable -> int {
+ return io_ctx->exec(
+ oid, cls_handler, std::string(cls).c_str(),
+ std::string(method).c_str(), inbl,
+ (out != nullptr ? out : outbl), snap_id, snapc);
+ };
+ if (ec != nullptr) {
+ op = std::bind(
+ save_operation_ec, std::bind(op, _1, _2, _3, _4, _5, _6), ec);
+ }
+ o->ops.push_back(op);
+}
+
+void Op::exec(std::string_view cls, std::string_view method,
+ const ceph::buffer::list& inbl,
+ boost::system::error_code* ec) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+
+ auto cls_handler = librados_test_stub::get_class_handler();
+ librados::ObjectOperationTestImpl op =
+ [cls_handler, cls, method, inbl = const_cast<bufferlist&>(inbl)]
+ (librados::TestIoCtxImpl* io_ctx, const std::string& oid, bufferlist* outbl,
+ uint64_t snap_id, const SnapContext& snapc, uint64_t*) mutable -> int {
+ return io_ctx->exec(
+ oid, cls_handler, std::string(cls).c_str(),
+ std::string(method).c_str(), inbl, outbl, snap_id, snapc);
+ };
+ if (ec != NULL) {
+ op = std::bind(
+ save_operation_ec, std::bind(op, _1, _2, _3, _4, _5, _6), ec);
+ }
+ o->ops.push_back(op);
+}
+
+void ReadOp::read(size_t off, uint64_t len, ceph::buffer::list* out,
+ boost::system::error_code* ec) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ librados::ObjectOperationTestImpl op;
+ if (out != nullptr) {
+ op = std::bind(
+ &librados::TestIoCtxImpl::read, _1, _2, len, off, out, _4, _6);
+ } else {
+ op = std::bind(
+ &librados::TestIoCtxImpl::read, _1, _2, len, off, _3, _4, _6);
+ }
+
+ if (ec != NULL) {
+ op = std::bind(
+ save_operation_ec, std::bind(op, _1, _2, _3, _4, _5, _6), ec);
+ }
+ o->ops.push_back(op);
+}
+
+void ReadOp::sparse_read(uint64_t off, uint64_t len,
+ ceph::buffer::list* out,
+ std::vector<std::pair<std::uint64_t,
+ std::uint64_t>>* extents,
+ boost::system::error_code* ec) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ librados::ObjectOperationTestImpl op =
+ [off, len, out, extents]
+ (librados::TestIoCtxImpl* io_ctx, const std::string& oid, bufferlist* outbl,
+ uint64_t snap_id, const SnapContext& snapc, uint64_t*) mutable -> int {
+ std::map<uint64_t,uint64_t> m;
+ int r = io_ctx->sparse_read(
+ oid, off, len, &m, (out != nullptr ? out : outbl), snap_id);
+ if (r >= 0 && extents != nullptr) {
+ extents->clear();
+ extents->insert(extents->end(), m.begin(), m.end());
+ }
+ return r;
+ };
+ if (ec != NULL) {
+ op = std::bind(save_operation_ec,
+ std::bind(op, _1, _2, _3, _4, _5, _6), ec);
+ }
+ o->ops.push_back(op);
+}
+
+void ReadOp::list_snaps(SnapSet* snaps, bs::error_code* ec) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ librados::ObjectOperationTestImpl op =
+ [snaps]
+ (librados::TestIoCtxImpl* io_ctx, const std::string& oid, bufferlist*,
+ uint64_t, const SnapContext&, uint64_t*) mutable -> int {
+ librados::snap_set_t snap_set;
+ int r = io_ctx->list_snaps(oid, &snap_set);
+ if (r >= 0 && snaps != nullptr) {
+ *snaps = {};
+ snaps->seq = snap_set.seq;
+ snaps->clones.reserve(snap_set.clones.size());
+ for (auto& clone : snap_set.clones) {
+ neorados::CloneInfo clone_info;
+ clone_info.cloneid = clone.cloneid;
+ clone_info.snaps = clone.snaps;
+ clone_info.overlap = clone.overlap;
+ clone_info.size = clone.size;
+ snaps->clones.push_back(clone_info);
+ }
+ }
+ return r;
+ };
+ if (ec != NULL) {
+ op = std::bind(save_operation_ec,
+ std::bind(op, _1, _2, _3, _4, _5, _6), ec);
+ }
+ o->ops.push_back(op);
+}
+
+void WriteOp::create(bool exclusive) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ o->ops.push_back(std::bind(
+ &librados::TestIoCtxImpl::create, _1, _2, exclusive, _5));
+}
+
+void WriteOp::write(uint64_t off, ceph::buffer::list&& bl) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ o->ops.push_back(std::bind(
+ &librados::TestIoCtxImpl::write, _1, _2, bl, bl.length(), off, _5));
+}
+
+void WriteOp::write_full(ceph::buffer::list&& bl) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ o->ops.push_back(std::bind(
+ &librados::TestIoCtxImpl::write_full, _1, _2, bl, _5));
+}
+
+void WriteOp::remove() {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ o->ops.push_back(std::bind(
+ &librados::TestIoCtxImpl::remove, _1, _2, _5));
+}
+
+void WriteOp::truncate(uint64_t off) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ o->ops.push_back(std::bind(
+ &librados::TestIoCtxImpl::truncate, _1, _2, off, _5));
+}
+
+void WriteOp::zero(uint64_t off, uint64_t len) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ o->ops.push_back(std::bind(
+ &librados::TestIoCtxImpl::zero, _1, _2, off, len, _5));
+}
+
+void WriteOp::writesame(std::uint64_t off, std::uint64_t write_len,
+ ceph::buffer::list&& bl) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ o->ops.push_back(std::bind(
+ &librados::TestIoCtxImpl::writesame, _1, _2, bl, write_len, off, _5));
+}
+
+void WriteOp::set_alloc_hint(uint64_t expected_object_size,
+ uint64_t expected_write_size,
+ alloc_hint::alloc_hint_t flags) {
+ // no-op
+}
+
+RADOS::RADOS() = default;
+
+RADOS::RADOS(RADOS&&) = default;
+
+RADOS::RADOS(std::unique_ptr<detail::Client> impl)
+ : impl(std::move(impl)) {
+}
+
+RADOS::~RADOS() = default;
+
+RADOS RADOS::make_with_librados(librados::Rados& rados) {
+ auto test_rados_client = reinterpret_cast<librados::TestRadosClient*>(
+ rados.client);
+ return RADOS{std::make_unique<detail::Client>(test_rados_client)};
+}
+
+CephContext* neorados::RADOS::cct() {
+ return impl->test_rados_client->cct();
+}
+
+boost::asio::io_context& neorados::RADOS::get_io_context() {
+ return impl->io_context;
+}
+
+boost::asio::io_context::executor_type neorados::RADOS::get_executor() const {
+ return impl->io_context.get_executor();
+}
+
+void RADOS::execute(const Object& o, const IOContext& ioc, ReadOp&& op,
+ ceph::buffer::list* bl, std::unique_ptr<Op::Completion> c,
+ uint64_t* objver, const blkin_trace_info* trace_info) {
+ auto io_ctx = impl->get_io_ctx(ioc);
+ if (io_ctx == nullptr) {
+ c->dispatch(std::move(c), osdc_errc::pool_dne);
+ return;
+ }
+
+ auto ops = *reinterpret_cast<librados::TestObjectOperationImpl**>(&op.impl);
+
+ auto snap_id = CEPH_NOSNAP;
+ auto opt_snap_id = ioc.read_snap();
+ if (opt_snap_id) {
+ snap_id = *opt_snap_id;
+ }
+
+ auto completion = create_aio_completion(std::move(c));
+ auto r = io_ctx->aio_operate_read(std::string{o}, *ops, completion, 0U, bl,
+ snap_id, objver);
+ ceph_assert(r == 0);
+}
+
+void RADOS::execute(const Object& o, const IOContext& ioc, WriteOp&& op,
+ std::unique_ptr<Op::Completion> c, uint64_t* objver,
+ const blkin_trace_info* trace_info) {
+ auto io_ctx = impl->get_io_ctx(ioc);
+ if (io_ctx == nullptr) {
+ c->dispatch(std::move(c), osdc_errc::pool_dne);
+ return;
+ }
+
+ auto ops = *reinterpret_cast<librados::TestObjectOperationImpl**>(&op.impl);
+
+ SnapContext snapc;
+ auto opt_snapc = ioc.write_snap_context();
+ if (opt_snapc) {
+ snapc.seq = opt_snapc->first;
+ snapc.snaps.assign(opt_snapc->second.begin(), opt_snapc->second.end());
+ }
+
+ auto completion = create_aio_completion(std::move(c));
+ auto r = io_ctx->aio_operate(std::string{o}, *ops, completion, &snapc, nullptr, 0U);
+ ceph_assert(r == 0);
+}
+
+void RADOS::mon_command(std::vector<std::string> command,
+ const bufferlist& bl,
+ std::string* outs, bufferlist* outbl,
+ std::unique_ptr<Op::Completion> c) {
+ auto r = impl->test_rados_client->mon_command(command, bl, outbl, outs);
+ c->post(std::move(c),
+ (r < 0 ? bs::error_code(-r, osd_category()) : bs::error_code()));
+}
+
+void RADOS::blocklist_add(std::string_view client_address,
+ std::optional<std::chrono::seconds> expire,
+ std::unique_ptr<SimpleOpComp> c) {
+ auto r = impl->test_rados_client->blocklist_add(
+ std::string(client_address), expire.value_or(0s).count());
+ c->post(std::move(c),
+ (r < 0 ? bs::error_code(-r, mon_category()) : bs::error_code()));
+}
+
+void RADOS::wait_for_latest_osd_map(std::unique_ptr<Op::Completion> c) {
+ auto r = impl->test_rados_client->wait_for_latest_osd_map();
+ c->dispatch(std::move(c),
+ (r < 0 ? bs::error_code(-r, osd_category()) :
+ bs::error_code()));
+}
+
+} // namespace neorados
+
+namespace librados {
+
+MockTestMemIoCtxImpl& get_mock_io_ctx(neorados::RADOS& rados,
+ neorados::IOContext& io_context) {
+ auto& impl = *reinterpret_cast<std::unique_ptr<neorados::detail::Client>*>(
+ &rados);
+ auto io_ctx = impl->get_io_ctx(io_context);
+ ceph_assert(io_ctx != nullptr);
+ return *reinterpret_cast<MockTestMemIoCtxImpl*>(io_ctx);
+}
+
+MockTestMemRadosClient& get_mock_rados_client(neorados::RADOS& rados) {
+ auto& impl = *reinterpret_cast<std::unique_ptr<neorados::detail::Client>*>(
+ &rados);
+ return *reinterpret_cast<MockTestMemRadosClient*>(impl->test_rados_client);
+}
+
+} // namespace librados
diff --git a/src/test/librados_test_stub/TestClassHandler.cc b/src/test/librados_test_stub/TestClassHandler.cc
new file mode 100644
index 000000000..df830918f
--- /dev/null
+++ b/src/test/librados_test_stub/TestClassHandler.cc
@@ -0,0 +1,159 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librados_test_stub/TestClassHandler.h"
+#include "test/librados_test_stub/TestIoCtxImpl.h"
+#include <boost/algorithm/string/predicate.hpp>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include "common/debug.h"
+#include "include/ceph_assert.h"
+#include "include/dlfcn_compat.h"
+
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_rados
+
+namespace librados {
+
+TestClassHandler::TestClassHandler() {
+}
+
+TestClassHandler::~TestClassHandler() {
+ for (ClassHandles::iterator it = m_class_handles.begin();
+ it != m_class_handles.end(); ++it) {
+ dlclose(*it);
+ }
+}
+
+void TestClassHandler::open_class(const std::string& name,
+ const std::string& path) {
+ void *handle = dlopen(path.c_str(), RTLD_NOW);
+ if (handle == NULL) {
+ std::cerr << "Failed to load class: " << name << " (" << path << "): "
+ << dlerror() << std::endl;
+ return;
+ }
+
+ // initialize
+ void (*cls_init)() = reinterpret_cast<void (*)()>(
+ dlsym(handle, "__cls_init"));
+
+ if (!cls_init) {
+ std::cerr << "Error locating initializer: " << dlerror() << std::endl;
+ } else if (cls_init) {
+ m_class_handles.push_back(handle);
+ cls_init();
+ return;
+ }
+
+ std::cerr << "Class: " << name << " (" << path << ") missing initializer"
+ << std::endl;
+ dlclose(handle);
+}
+
+void TestClassHandler::open_all_classes() {
+ ceph_assert(m_class_handles.empty());
+
+ const char* env = getenv("CEPH_LIB");
+ std::string CEPH_LIB(env ? env : "lib");
+ DIR *dir = ::opendir(CEPH_LIB.c_str());
+ if (dir == NULL) {
+ ceph_abort();;
+ }
+
+ std::set<std::string> names;
+ struct dirent *pde = nullptr;
+ while ((pde = ::readdir(dir))) {
+ std::string name(pde->d_name);
+ if (!boost::algorithm::starts_with(name, "libcls_") ||
+ !boost::algorithm::ends_with(name, SHARED_LIB_SUFFIX)) {
+ continue;
+ }
+ names.insert(name);
+ }
+
+ for (auto& name : names) {
+ std::string class_name = name.substr(7, name.size() - 10);
+ open_class(class_name, CEPH_LIB + "/" + name);
+ }
+ closedir(dir);
+}
+
+int TestClassHandler::create(const std::string &name, cls_handle_t *handle) {
+ if (m_classes.find(name) != m_classes.end()) {
+ std::cerr << "Class " << name << " already exists" << std::endl;
+ return -EEXIST;
+ }
+
+ SharedClass cls(new Class());
+ m_classes[name] = cls;
+ *handle = reinterpret_cast<cls_handle_t>(cls.get());
+ return 0;
+}
+
+int TestClassHandler::create_method(cls_handle_t hclass,
+ const char *name,
+ cls_method_cxx_call_t class_call,
+ cls_method_handle_t *handle) {
+ Class *cls = reinterpret_cast<Class*>(hclass);
+ if (cls->methods.find(name) != cls->methods.end()) {
+ std::cerr << "Class method " << hclass << ":" << name << " already exists"
+ << std::endl;
+ return -EEXIST;
+ }
+
+ SharedMethod method(new Method());
+ method->class_call = class_call;
+ cls->methods[name] = method;
+ return 0;
+}
+
+cls_method_cxx_call_t TestClassHandler::get_method(const std::string &cls,
+ const std::string &method) {
+ Classes::iterator c_it = m_classes.find(cls);
+ if (c_it == m_classes.end()) {
+ std::cerr << "Failed to located class " << cls << std::endl;
+ return NULL;
+ }
+
+ SharedClass scls = c_it->second;
+ Methods::iterator m_it = scls->methods.find(method);
+ if (m_it == scls->methods.end()) {
+ std::cerr << "Failed to located class method" << cls << "." << method
+ << std::endl;
+ return NULL;
+ }
+ return m_it->second->class_call;
+}
+
+TestClassHandler::SharedMethodContext TestClassHandler::get_method_context(
+ TestIoCtxImpl *io_ctx_impl, const std::string &oid, uint64_t snap_id,
+ const SnapContext &snapc) {
+ SharedMethodContext ctx(new MethodContext());
+
+ // clone to ioctx to provide a firewall for gmock expectations
+ ctx->io_ctx_impl = io_ctx_impl->clone();
+ ctx->oid = oid;
+ ctx->snap_id = snap_id;
+ ctx->snapc = snapc;
+ return ctx;
+}
+
+int TestClassHandler::create_filter(cls_handle_t hclass,
+ const std::string& name,
+ cls_cxx_filter_factory_t fn)
+{
+ Class *cls = reinterpret_cast<Class*>(hclass);
+ if (cls->filters.find(name) != cls->filters.end()) {
+ return -EEXIST;
+ }
+ cls->filters[name] = fn;
+ return 0;
+}
+
+TestClassHandler::MethodContext::~MethodContext() {
+ io_ctx_impl->put();
+}
+
+} // namespace librados
diff --git a/src/test/librados_test_stub/TestClassHandler.h b/src/test/librados_test_stub/TestClassHandler.h
new file mode 100644
index 000000000..09e009bf8
--- /dev/null
+++ b/src/test/librados_test_stub/TestClassHandler.h
@@ -0,0 +1,79 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_CLASS_HANDLER_H
+#define CEPH_TEST_CLASS_HANDLER_H
+
+#include "objclass/objclass.h"
+#include "common/snap_types.h"
+#include <boost/shared_ptr.hpp>
+#include <list>
+#include <map>
+#include <string>
+
+namespace librados
+{
+
+class TestIoCtxImpl;
+
+class TestClassHandler {
+public:
+
+ TestClassHandler();
+ ~TestClassHandler();
+
+ struct MethodContext {
+ ~MethodContext();
+
+ TestIoCtxImpl *io_ctx_impl;
+ std::string oid;
+ uint64_t snap_id;
+ SnapContext snapc;
+ };
+ typedef boost::shared_ptr<MethodContext> SharedMethodContext;
+
+ struct Method {
+ cls_method_cxx_call_t class_call;
+ };
+ typedef boost::shared_ptr<Method> SharedMethod;
+ typedef std::map<std::string, SharedMethod> Methods;
+ typedef std::map<std::string, cls_cxx_filter_factory_t> Filters;
+
+ struct Class {
+ Methods methods;
+ Filters filters;
+ };
+ typedef boost::shared_ptr<Class> SharedClass;
+
+ void open_all_classes();
+
+ int create(const std::string &name, cls_handle_t *handle);
+ int create_method(cls_handle_t hclass, const char *method,
+ cls_method_cxx_call_t class_call,
+ cls_method_handle_t *handle);
+ cls_method_cxx_call_t get_method(const std::string &cls,
+ const std::string &method);
+ SharedMethodContext get_method_context(TestIoCtxImpl *io_ctx_impl,
+ const std::string &oid,
+ uint64_t snap_id,
+ const SnapContext &snapc);
+
+ int create_filter(cls_handle_t hclass, const std::string& filter_name,
+ cls_cxx_filter_factory_t fn);
+
+private:
+
+ typedef std::map<std::string, SharedClass> Classes;
+ typedef std::list<void*> ClassHandles;
+
+ Classes m_classes;
+ ClassHandles m_class_handles;
+ Filters m_filters;
+
+ void open_class(const std::string& name, const std::string& path);
+
+};
+
+} // namespace librados
+
+#endif // CEPH_TEST_CLASS_HANDLER_H
diff --git a/src/test/librados_test_stub/TestCluster.h b/src/test/librados_test_stub/TestCluster.h
new file mode 100644
index 000000000..9b7612d31
--- /dev/null
+++ b/src/test/librados_test_stub/TestCluster.h
@@ -0,0 +1,64 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_CLUSTER_H
+#define CEPH_TEST_CLUSTER_H
+
+#include "test/librados_test_stub/TestWatchNotify.h"
+#include "include/common_fwd.h"
+
+namespace librados {
+
+class TestRadosClient;
+class TestWatchNotify;
+
+class TestCluster {
+public:
+ struct ObjectLocator {
+ std::string nspace;
+ std::string name;
+
+ ObjectLocator(const std::string& nspace, const std::string& name)
+ : nspace(nspace), name(name) {
+ }
+
+ bool operator<(const ObjectLocator& rhs) const {
+ if (nspace != rhs.nspace) {
+ return nspace < rhs.nspace;
+ }
+ return name < rhs.name;
+ }
+ };
+
+ struct ObjectHandler {
+ virtual ~ObjectHandler() {}
+
+ virtual void handle_removed(TestRadosClient* test_rados_client) = 0;
+ };
+
+ TestCluster() : m_watch_notify(this) {
+ }
+ virtual ~TestCluster() {
+ }
+
+ virtual TestRadosClient *create_rados_client(CephContext *cct) = 0;
+
+ virtual int register_object_handler(int64_t pool_id,
+ const ObjectLocator& locator,
+ ObjectHandler* object_handler) = 0;
+ virtual void unregister_object_handler(int64_t pool_id,
+ const ObjectLocator& locator,
+ ObjectHandler* object_handler) = 0;
+
+ TestWatchNotify *get_watch_notify() {
+ return &m_watch_notify;
+ }
+
+protected:
+ TestWatchNotify m_watch_notify;
+
+};
+
+} // namespace librados
+
+#endif // CEPH_TEST_CLUSTER_H
diff --git a/src/test/librados_test_stub/TestIoCtxImpl.cc b/src/test/librados_test_stub/TestIoCtxImpl.cc
new file mode 100644
index 000000000..8a953ff7c
--- /dev/null
+++ b/src/test/librados_test_stub/TestIoCtxImpl.cc
@@ -0,0 +1,394 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librados_test_stub/TestIoCtxImpl.h"
+#include "test/librados_test_stub/TestClassHandler.h"
+#include "test/librados_test_stub/TestRadosClient.h"
+#include "test/librados_test_stub/TestWatchNotify.h"
+#include "librados/AioCompletionImpl.h"
+#include "include/ceph_assert.h"
+#include "common/Finisher.h"
+#include "common/valgrind.h"
+#include "objclass/objclass.h"
+#include <functional>
+#include <errno.h>
+
+using namespace std;
+
+namespace librados {
+
+TestIoCtxImpl::TestIoCtxImpl() : m_client(NULL) {
+ get();
+}
+
+TestIoCtxImpl::TestIoCtxImpl(TestRadosClient *client, int64_t pool_id,
+ const std::string& pool_name)
+ : m_client(client), m_pool_id(pool_id), m_pool_name(pool_name),
+ m_snap_seq(CEPH_NOSNAP)
+{
+ m_client->get();
+ get();
+}
+
+TestIoCtxImpl::TestIoCtxImpl(const TestIoCtxImpl& rhs)
+ : m_client(rhs.m_client),
+ m_pool_id(rhs.m_pool_id),
+ m_pool_name(rhs.m_pool_name),
+ m_namespace_name(rhs.m_namespace_name),
+ m_snap_seq(rhs.m_snap_seq)
+{
+ m_client->get();
+ get();
+}
+
+TestIoCtxImpl::~TestIoCtxImpl() {
+ ceph_assert(m_pending_ops == 0);
+}
+
+void TestObjectOperationImpl::get() {
+ m_refcount++;
+}
+
+void TestObjectOperationImpl::put() {
+ if (--m_refcount == 0) {
+ ANNOTATE_HAPPENS_AFTER(&m_refcount);
+ ANNOTATE_HAPPENS_BEFORE_FORGET_ALL(&m_refcount);
+ delete this;
+ } else {
+ ANNOTATE_HAPPENS_BEFORE(&m_refcount);
+ }
+}
+
+void TestIoCtxImpl::get() {
+ m_refcount++;
+}
+
+void TestIoCtxImpl::put() {
+ if (--m_refcount == 0) {
+ m_client->put();
+ delete this;
+ }
+}
+
+uint64_t TestIoCtxImpl::get_instance_id() const {
+ return m_client->get_instance_id();
+}
+
+int64_t TestIoCtxImpl::get_id() {
+ return m_pool_id;
+}
+
+uint64_t TestIoCtxImpl::get_last_version() {
+ return 0;
+}
+
+std::string TestIoCtxImpl::get_pool_name() {
+ return m_pool_name;
+}
+
+int TestIoCtxImpl::aio_flush() {
+ m_client->flush_aio_operations();
+ return 0;
+}
+
+void TestIoCtxImpl::aio_flush_async(AioCompletionImpl *c) {
+ m_client->flush_aio_operations(c);
+}
+
+void TestIoCtxImpl::aio_notify(const std::string& oid, AioCompletionImpl *c,
+ bufferlist& bl, uint64_t timeout_ms,
+ bufferlist *pbl) {
+ m_pending_ops++;
+ c->get();
+ C_AioNotify *ctx = new C_AioNotify(this, c);
+ m_client->get_watch_notify()->aio_notify(m_client, m_pool_id, get_namespace(),
+ oid, bl, timeout_ms, pbl, ctx);
+}
+
+int TestIoCtxImpl::aio_operate(const std::string& oid, TestObjectOperationImpl &ops,
+ AioCompletionImpl *c, SnapContext *snap_context,
+ const ceph::real_time *pmtime, int flags) {
+ // TODO flags for now
+ ops.get();
+ m_pending_ops++;
+ m_client->add_aio_operation(oid, true, std::bind(
+ &TestIoCtxImpl::execute_aio_operations, this, oid, &ops,
+ reinterpret_cast<bufferlist*>(0), m_snap_seq,
+ snap_context != NULL ? *snap_context : m_snapc, nullptr), c);
+ return 0;
+}
+
+int TestIoCtxImpl::aio_operate_read(const std::string& oid,
+ TestObjectOperationImpl &ops,
+ AioCompletionImpl *c, int flags,
+ bufferlist *pbl, uint64_t snap_id,
+ uint64_t* objver) {
+ // TODO ignoring flags for now
+ ops.get();
+ m_pending_ops++;
+ m_client->add_aio_operation(oid, true, std::bind(
+ &TestIoCtxImpl::execute_aio_operations, this, oid, &ops, pbl, snap_id,
+ m_snapc, objver), c);
+ return 0;
+}
+
+int TestIoCtxImpl::aio_watch(const std::string& o, AioCompletionImpl *c,
+ uint64_t *handle, librados::WatchCtx2 *watch_ctx) {
+ m_pending_ops++;
+ c->get();
+ C_AioNotify *ctx = new C_AioNotify(this, c);
+ if (m_client->is_blocklisted()) {
+ m_client->get_aio_finisher()->queue(ctx, -EBLOCKLISTED);
+ } else {
+ m_client->get_watch_notify()->aio_watch(m_client, m_pool_id,
+ get_namespace(), o,
+ get_instance_id(), handle, nullptr,
+ watch_ctx, ctx);
+ }
+ return 0;
+}
+
+int TestIoCtxImpl::aio_unwatch(uint64_t handle, AioCompletionImpl *c) {
+ m_pending_ops++;
+ c->get();
+ C_AioNotify *ctx = new C_AioNotify(this, c);
+ if (m_client->is_blocklisted()) {
+ m_client->get_aio_finisher()->queue(ctx, -EBLOCKLISTED);
+ } else {
+ m_client->get_watch_notify()->aio_unwatch(m_client, handle, ctx);
+ }
+ return 0;
+}
+
+int TestIoCtxImpl::exec(const std::string& oid, TestClassHandler *handler,
+ const char *cls, const char *method,
+ bufferlist& inbl, bufferlist* outbl,
+ uint64_t snap_id, const SnapContext &snapc) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ cls_method_cxx_call_t call = handler->get_method(cls, method);
+ if (call == NULL) {
+ return -ENOSYS;
+ }
+
+ return (*call)(reinterpret_cast<cls_method_context_t>(
+ handler->get_method_context(this, oid, snap_id, snapc).get()), &inbl,
+ outbl);
+}
+
+int TestIoCtxImpl::list_watchers(const std::string& o,
+ std::list<obj_watch_t> *out_watchers) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ return m_client->get_watch_notify()->list_watchers(m_pool_id, get_namespace(),
+ o, out_watchers);
+}
+
+int TestIoCtxImpl::notify(const std::string& o, bufferlist& bl,
+ uint64_t timeout_ms, bufferlist *pbl) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ return m_client->get_watch_notify()->notify(m_client, m_pool_id,
+ get_namespace(), o, bl,
+ timeout_ms, pbl);
+}
+
+void TestIoCtxImpl::notify_ack(const std::string& o, uint64_t notify_id,
+ uint64_t handle, bufferlist& bl) {
+ m_client->get_watch_notify()->notify_ack(m_client, m_pool_id, get_namespace(),
+ o, notify_id, handle,
+ m_client->get_instance_id(), bl);
+}
+
+int TestIoCtxImpl::operate(const std::string& oid,
+ TestObjectOperationImpl &ops) {
+ AioCompletionImpl *comp = new AioCompletionImpl();
+
+ ops.get();
+ m_pending_ops++;
+ m_client->add_aio_operation(oid, false, std::bind(
+ &TestIoCtxImpl::execute_aio_operations, this, oid, &ops,
+ reinterpret_cast<bufferlist*>(0), m_snap_seq, m_snapc, nullptr), comp);
+
+ comp->wait_for_complete();
+ int ret = comp->get_return_value();
+ comp->put();
+ return ret;
+}
+
+int TestIoCtxImpl::operate_read(const std::string& oid,
+ TestObjectOperationImpl &ops,
+ bufferlist *pbl) {
+ AioCompletionImpl *comp = new AioCompletionImpl();
+
+ ops.get();
+ m_pending_ops++;
+ m_client->add_aio_operation(oid, false, std::bind(
+ &TestIoCtxImpl::execute_aio_operations, this, oid, &ops, pbl,
+ m_snap_seq, m_snapc, nullptr), comp);
+
+ comp->wait_for_complete();
+ int ret = comp->get_return_value();
+ comp->put();
+ return ret;
+}
+
+void TestIoCtxImpl::aio_selfmanaged_snap_create(uint64_t *snapid,
+ AioCompletionImpl *c) {
+ m_client->add_aio_operation(
+ "", true,
+ std::bind(&TestIoCtxImpl::selfmanaged_snap_create, this, snapid), c);
+}
+
+void TestIoCtxImpl::aio_selfmanaged_snap_remove(uint64_t snapid,
+ AioCompletionImpl *c) {
+ m_client->add_aio_operation(
+ "", true,
+ std::bind(&TestIoCtxImpl::selfmanaged_snap_remove, this, snapid), c);
+}
+
+int TestIoCtxImpl::selfmanaged_snap_set_write_ctx(snap_t seq,
+ std::vector<snap_t>& snaps) {
+ std::vector<snapid_t> snap_ids(snaps.begin(), snaps.end());
+ m_snapc = SnapContext(seq, snap_ids);
+ return 0;
+}
+
+int TestIoCtxImpl::set_alloc_hint(const std::string& oid,
+ uint64_t expected_object_size,
+ uint64_t expected_write_size,
+ uint32_t flags,
+ const SnapContext &snapc) {
+ return 0;
+}
+
+void TestIoCtxImpl::set_snap_read(snap_t seq) {
+ if (seq == 0) {
+ seq = CEPH_NOSNAP;
+ }
+ m_snap_seq = seq;
+}
+
+int TestIoCtxImpl::tmap_update(const std::string& oid, bufferlist& cmdbl) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ // TODO: protect against concurrent tmap updates
+ bufferlist tmap_header;
+ std::map<string,bufferlist> tmap;
+ uint64_t size = 0;
+ int r = stat(oid, &size, NULL);
+ if (r == -ENOENT) {
+ r = create(oid, false, m_snapc);
+ }
+ if (r < 0) {
+ return r;
+ }
+
+ if (size > 0) {
+ bufferlist inbl;
+ r = read(oid, size, 0, &inbl, CEPH_NOSNAP, nullptr);
+ if (r < 0) {
+ return r;
+ }
+ auto iter = inbl.cbegin();
+ decode(tmap_header, iter);
+ decode(tmap, iter);
+ }
+
+ __u8 c;
+ std::string key;
+ bufferlist value;
+ auto iter = cmdbl.cbegin();
+ decode(c, iter);
+ decode(key, iter);
+
+ switch (c) {
+ case CEPH_OSD_TMAP_SET:
+ decode(value, iter);
+ tmap[key] = value;
+ break;
+ case CEPH_OSD_TMAP_RM:
+ r = tmap.erase(key);
+ if (r == 0) {
+ return -ENOENT;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ bufferlist out;
+ encode(tmap_header, out);
+ encode(tmap, out);
+ r = write_full(oid, out, m_snapc);
+ return r;
+}
+
+int TestIoCtxImpl::unwatch(uint64_t handle) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ return m_client->get_watch_notify()->unwatch(m_client, handle);
+}
+
+int TestIoCtxImpl::watch(const std::string& o, uint64_t *handle,
+ librados::WatchCtx *ctx, librados::WatchCtx2 *ctx2) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ return m_client->get_watch_notify()->watch(m_client, m_pool_id,
+ get_namespace(), o,
+ get_instance_id(), handle, ctx,
+ ctx2);
+}
+
+int TestIoCtxImpl::execute_operation(const std::string& oid,
+ const Operation &operation) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ TestRadosClient::Transaction transaction(m_client, get_namespace(), oid);
+ return operation(this, oid);
+}
+
+int TestIoCtxImpl::execute_aio_operations(const std::string& oid,
+ TestObjectOperationImpl *ops,
+ bufferlist *pbl, uint64_t snap_id,
+ const SnapContext &snapc,
+ uint64_t* objver) {
+ int ret = 0;
+ if (m_client->is_blocklisted()) {
+ ret = -EBLOCKLISTED;
+ } else {
+ TestRadosClient::Transaction transaction(m_client, get_namespace(), oid);
+ for (ObjectOperations::iterator it = ops->ops.begin();
+ it != ops->ops.end(); ++it) {
+ ret = (*it)(this, oid, pbl, snap_id, snapc, objver);
+ if (ret < 0) {
+ break;
+ }
+ }
+ }
+ m_pending_ops--;
+ ops->put();
+ return ret;
+}
+
+void TestIoCtxImpl::handle_aio_notify_complete(AioCompletionImpl *c, int r) {
+ m_pending_ops--;
+
+ m_client->finish_aio_completion(c, r);
+}
+
+} // namespace librados
diff --git a/src/test/librados_test_stub/TestIoCtxImpl.h b/src/test/librados_test_stub/TestIoCtxImpl.h
new file mode 100644
index 000000000..fdda17cfd
--- /dev/null
+++ b/src/test/librados_test_stub/TestIoCtxImpl.h
@@ -0,0 +1,221 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_IO_CTX_IMPL_H
+#define CEPH_TEST_IO_CTX_IMPL_H
+
+#include <list>
+#include <atomic>
+
+#include <boost/function.hpp>
+
+#include "include/rados/librados.hpp"
+#include "include/Context.h"
+#include "common/snap_types.h"
+
+namespace librados {
+
+class TestClassHandler;
+class TestIoCtxImpl;
+class TestRadosClient;
+
+typedef boost::function<int(TestIoCtxImpl*,
+ const std::string&,
+ bufferlist *,
+ uint64_t,
+ const SnapContext &,
+ uint64_t*)> ObjectOperationTestImpl;
+typedef std::list<ObjectOperationTestImpl> ObjectOperations;
+
+struct TestObjectOperationImpl {
+public:
+ void get();
+ void put();
+
+ ObjectOperations ops;
+private:
+ std::atomic<uint64_t> m_refcount = { 0 };
+};
+
+class TestIoCtxImpl {
+public:
+ typedef boost::function<int(TestIoCtxImpl *, const std::string &)> Operation;
+
+
+ TestIoCtxImpl();
+ explicit TestIoCtxImpl(TestRadosClient *client, int64_t m_pool_id,
+ const std::string& pool_name);
+
+ TestRadosClient *get_rados_client() {
+ return m_client;
+ }
+
+ void get();
+ void put();
+
+ inline int64_t get_pool_id() const {
+ return m_pool_id;
+ }
+
+ virtual TestIoCtxImpl *clone() = 0;
+
+ virtual uint64_t get_instance_id() const;
+ virtual int64_t get_id();
+ virtual uint64_t get_last_version();
+ virtual std::string get_pool_name();
+
+ inline void set_namespace(const std::string& namespace_name) {
+ m_namespace_name = namespace_name;
+ }
+ inline std::string get_namespace() const {
+ return m_namespace_name;
+ }
+
+ snap_t get_snap_read() const {
+ return m_snap_seq;
+ }
+
+ inline void set_snap_context(const SnapContext& snapc) {
+ m_snapc = snapc;
+ }
+ const SnapContext &get_snap_context() const {
+ return m_snapc;
+ }
+
+ virtual int aio_flush();
+ virtual void aio_flush_async(AioCompletionImpl *c);
+ virtual void aio_notify(const std::string& oid, AioCompletionImpl *c,
+ bufferlist& bl, uint64_t timeout_ms, bufferlist *pbl);
+ virtual int aio_operate(const std::string& oid, TestObjectOperationImpl &ops,
+ AioCompletionImpl *c, SnapContext *snap_context,
+ const ceph::real_time *pmtime, int flags);
+ virtual int aio_operate_read(const std::string& oid, TestObjectOperationImpl &ops,
+ AioCompletionImpl *c, int flags,
+ bufferlist *pbl, uint64_t snap_id,
+ uint64_t* objver);
+ virtual int aio_remove(const std::string& oid, AioCompletionImpl *c,
+ int flags = 0) = 0;
+ virtual int aio_watch(const std::string& o, AioCompletionImpl *c,
+ uint64_t *handle, librados::WatchCtx2 *ctx);
+ virtual int aio_unwatch(uint64_t handle, AioCompletionImpl *c);
+ virtual int append(const std::string& oid, const bufferlist &bl,
+ const SnapContext &snapc) = 0;
+ virtual int assert_exists(const std::string &oid, uint64_t snap_id) = 0;
+ virtual int assert_version(const std::string &oid, uint64_t ver) = 0;
+
+ virtual int create(const std::string& oid, bool exclusive,
+ const SnapContext &snapc) = 0;
+ virtual int exec(const std::string& oid, TestClassHandler *handler,
+ const char *cls, const char *method,
+ bufferlist& inbl, bufferlist* outbl,
+ uint64_t snap_id, const SnapContext &snapc);
+ virtual int list_snaps(const std::string& o, snap_set_t *out_snaps) = 0;
+ virtual int list_watchers(const std::string& o,
+ std::list<obj_watch_t> *out_watchers);
+ virtual int notify(const std::string& o, bufferlist& bl,
+ uint64_t timeout_ms, bufferlist *pbl);
+ virtual void notify_ack(const std::string& o, uint64_t notify_id,
+ uint64_t handle, bufferlist& bl);
+ virtual int omap_get_vals(const std::string& oid,
+ const std::string& start_after,
+ const std::string &filter_prefix,
+ uint64_t max_return,
+ std::map<std::string, bufferlist> *out_vals) = 0;
+ virtual int omap_get_vals2(const std::string& oid,
+ const std::string& start_after,
+ const std::string &filter_prefix,
+ uint64_t max_return,
+ std::map<std::string, bufferlist> *out_vals,
+ bool *pmore) = 0;
+ virtual int omap_rm_keys(const std::string& oid,
+ const std::set<std::string>& keys) = 0;
+ virtual int omap_set(const std::string& oid,
+ const std::map<std::string, bufferlist> &map) = 0;
+ virtual int operate(const std::string& oid, TestObjectOperationImpl &ops);
+ virtual int operate_read(const std::string& oid, TestObjectOperationImpl &ops,
+ bufferlist *pbl);
+ virtual int read(const std::string& oid, size_t len, uint64_t off,
+ bufferlist *bl, uint64_t snap_id, uint64_t* objver) = 0;
+ virtual int remove(const std::string& oid, const SnapContext &snapc) = 0;
+ virtual int selfmanaged_snap_create(uint64_t *snapid) = 0;
+ virtual void aio_selfmanaged_snap_create(uint64_t *snapid,
+ AioCompletionImpl *c);
+ virtual int selfmanaged_snap_remove(uint64_t snapid) = 0;
+ virtual void aio_selfmanaged_snap_remove(uint64_t snapid,
+ AioCompletionImpl *c);
+ virtual int selfmanaged_snap_rollback(const std::string& oid,
+ uint64_t snapid) = 0;
+ virtual int selfmanaged_snap_set_write_ctx(snap_t seq,
+ std::vector<snap_t>& snaps);
+ virtual int set_alloc_hint(const std::string& oid,
+ uint64_t expected_object_size,
+ uint64_t expected_write_size,
+ uint32_t flags,
+ const SnapContext &snapc);
+ virtual void set_snap_read(snap_t seq);
+ virtual int sparse_read(const std::string& oid, uint64_t off, uint64_t len,
+ std::map<uint64_t,uint64_t> *m,
+ bufferlist *data_bl, uint64_t snap_id) = 0;
+ virtual int stat(const std::string& oid, uint64_t *psize, time_t *pmtime) = 0;
+ virtual int truncate(const std::string& oid, uint64_t size,
+ const SnapContext &snapc) = 0;
+ virtual int tmap_update(const std::string& oid, bufferlist& cmdbl);
+ virtual int unwatch(uint64_t handle);
+ virtual int watch(const std::string& o, uint64_t *handle,
+ librados::WatchCtx *ctx, librados::WatchCtx2 *ctx2);
+ virtual int write(const std::string& oid, bufferlist& bl, size_t len,
+ uint64_t off, const SnapContext &snapc) = 0;
+ virtual int write_full(const std::string& oid, bufferlist& bl,
+ const SnapContext &snapc) = 0;
+ virtual int writesame(const std::string& oid, bufferlist& bl, size_t len,
+ uint64_t off, const SnapContext &snapc) = 0;
+ virtual int cmpext(const std::string& oid, uint64_t off, bufferlist& cmp_bl,
+ uint64_t snap_id) = 0;
+ virtual int xattr_get(const std::string& oid,
+ std::map<std::string, bufferlist>* attrset) = 0;
+ virtual int xattr_set(const std::string& oid, const std::string &name,
+ bufferlist& bl) = 0;
+ virtual int zero(const std::string& oid, uint64_t off, uint64_t len,
+ const SnapContext &snapc) = 0;
+
+ int execute_operation(const std::string& oid,
+ const Operation &operation);
+
+protected:
+ TestIoCtxImpl(const TestIoCtxImpl& rhs);
+ virtual ~TestIoCtxImpl();
+
+ int execute_aio_operations(const std::string& oid,
+ TestObjectOperationImpl *ops,
+ bufferlist *pbl, uint64_t,
+ const SnapContext &snapc,
+ uint64_t* objver);
+
+private:
+ struct C_AioNotify : public Context {
+ TestIoCtxImpl *io_ctx;
+ AioCompletionImpl *aio_comp;
+ C_AioNotify(TestIoCtxImpl *_io_ctx, AioCompletionImpl *_aio_comp)
+ : io_ctx(_io_ctx), aio_comp(_aio_comp) {
+ }
+ void finish(int r) override {
+ io_ctx->handle_aio_notify_complete(aio_comp, r);
+ }
+ };
+
+ TestRadosClient *m_client;
+ int64_t m_pool_id = 0;
+ std::string m_pool_name;
+ std::string m_namespace_name;
+
+ snap_t m_snap_seq = 0;
+ SnapContext m_snapc;
+ std::atomic<uint64_t> m_refcount = { 0 };
+ std::atomic<uint64_t> m_pending_ops = { 0 };
+
+ void handle_aio_notify_complete(AioCompletionImpl *aio_comp, int r);
+};
+
+} // namespace librados
+
+#endif // CEPH_TEST_IO_CTX_IMPL_H
diff --git a/src/test/librados_test_stub/TestMemCluster.cc b/src/test/librados_test_stub/TestMemCluster.cc
new file mode 100644
index 000000000..f0995788b
--- /dev/null
+++ b/src/test/librados_test_stub/TestMemCluster.cc
@@ -0,0 +1,203 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librados_test_stub/TestMemCluster.h"
+#include "test/librados_test_stub/TestMemRadosClient.h"
+
+namespace librados {
+
+TestMemCluster::File::File()
+ : objver(0), snap_id(), exists(true) {
+}
+
+TestMemCluster::File::File(const File &rhs)
+ : data(rhs.data),
+ mtime(rhs.mtime),
+ objver(rhs.objver),
+ snap_id(rhs.snap_id),
+ exists(rhs.exists) {
+}
+
+TestMemCluster::Pool::Pool() = default;
+
+TestMemCluster::TestMemCluster()
+ : m_next_nonce(static_cast<uint32_t>(reinterpret_cast<uint64_t>(this))) {
+}
+
+TestMemCluster::~TestMemCluster() {
+ for (auto pool_pair : m_pools) {
+ pool_pair.second->put();
+ }
+}
+
+TestRadosClient *TestMemCluster::create_rados_client(CephContext *cct) {
+ return new TestMemRadosClient(cct, this);
+}
+
+int TestMemCluster::register_object_handler(int64_t pool_id,
+ const ObjectLocator& locator,
+ ObjectHandler* object_handler) {
+ std::lock_guard locker{m_lock};
+ auto pool = get_pool(m_lock, pool_id);
+ if (pool == nullptr) {
+ return -ENOENT;
+ }
+
+ std::unique_lock pool_locker{pool->file_lock};
+ auto file_it = pool->files.find(locator);
+ if (file_it == pool->files.end()) {
+ return -ENOENT;
+ }
+
+ auto& object_handlers = pool->file_handlers[locator];
+ auto it = object_handlers.find(object_handler);
+ ceph_assert(it == object_handlers.end());
+
+ object_handlers.insert(object_handler);
+ return 0;
+}
+
+void TestMemCluster::unregister_object_handler(int64_t pool_id,
+ const ObjectLocator& locator,
+ ObjectHandler* object_handler) {
+ std::lock_guard locker{m_lock};
+ auto pool = get_pool(m_lock, pool_id);
+ if (pool == nullptr) {
+ return;
+ }
+
+ std::unique_lock pool_locker{pool->file_lock};
+ auto handlers_it = pool->file_handlers.find(locator);
+ if (handlers_it == pool->file_handlers.end()) {
+ return;
+ }
+
+ auto& object_handlers = handlers_it->second;
+ object_handlers.erase(object_handler);
+}
+
+int TestMemCluster::pool_create(const std::string &pool_name) {
+ std::lock_guard locker{m_lock};
+ if (m_pools.find(pool_name) != m_pools.end()) {
+ return -EEXIST;
+ }
+ Pool *pool = new Pool();
+ pool->pool_id = ++m_pool_id;
+ m_pools[pool_name] = pool;
+ return 0;
+}
+
+int TestMemCluster::pool_delete(const std::string &pool_name) {
+ std::lock_guard locker{m_lock};
+ Pools::iterator iter = m_pools.find(pool_name);
+ if (iter == m_pools.end()) {
+ return -ENOENT;
+ }
+ iter->second->put();
+ m_pools.erase(iter);
+ return 0;
+}
+
+int TestMemCluster::pool_get_base_tier(int64_t pool_id, int64_t* base_tier) {
+ // TODO
+ *base_tier = pool_id;
+ return 0;
+}
+
+int TestMemCluster::pool_list(std::list<std::pair<int64_t, std::string> >& v) {
+ std::lock_guard locker{m_lock};
+ v.clear();
+ for (Pools::iterator iter = m_pools.begin(); iter != m_pools.end(); ++iter) {
+ v.push_back(std::make_pair(iter->second->pool_id, iter->first));
+ }
+ return 0;
+}
+
+int64_t TestMemCluster::pool_lookup(const std::string &pool_name) {
+ std::lock_guard locker{m_lock};
+ Pools::iterator iter = m_pools.find(pool_name);
+ if (iter == m_pools.end()) {
+ return -ENOENT;
+ }
+ return iter->second->pool_id;
+}
+
+int TestMemCluster::pool_reverse_lookup(int64_t id, std::string *name) {
+ std::lock_guard locker{m_lock};
+ for (Pools::iterator iter = m_pools.begin(); iter != m_pools.end(); ++iter) {
+ if (iter->second->pool_id == id) {
+ *name = iter->first;
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+TestMemCluster::Pool *TestMemCluster::get_pool(int64_t pool_id) {
+ std::lock_guard locker{m_lock};
+ return get_pool(m_lock, pool_id);
+}
+
+TestMemCluster::Pool *TestMemCluster::get_pool(const ceph::mutex& lock,
+ int64_t pool_id) {
+ for (auto &pool_pair : m_pools) {
+ if (pool_pair.second->pool_id == pool_id) {
+ return pool_pair.second;
+ }
+ }
+ return nullptr;
+}
+
+TestMemCluster::Pool *TestMemCluster::get_pool(const std::string &pool_name) {
+ std::lock_guard locker{m_lock};
+ Pools::iterator iter = m_pools.find(pool_name);
+ if (iter != m_pools.end()) {
+ return iter->second;
+ }
+ return nullptr;
+}
+
+void TestMemCluster::allocate_client(uint32_t *nonce, uint64_t *global_id) {
+ std::lock_guard locker{m_lock};
+ *nonce = m_next_nonce++;
+ *global_id = m_next_global_id++;
+}
+
+void TestMemCluster::deallocate_client(uint32_t nonce) {
+ std::lock_guard locker{m_lock};
+ m_blocklist.erase(nonce);
+}
+
+bool TestMemCluster::is_blocklisted(uint32_t nonce) const {
+ std::lock_guard locker{m_lock};
+ return (m_blocklist.find(nonce) != m_blocklist.end());
+}
+
+void TestMemCluster::blocklist(uint32_t nonce) {
+ {
+ std::lock_guard locker{m_lock};
+ m_blocklist.insert(nonce);
+ }
+
+ // after blocklisting the client, disconnect and drop its watches
+ m_watch_notify.blocklist(nonce);
+}
+
+void TestMemCluster::transaction_start(const ObjectLocator& locator) {
+ std::unique_lock locker{m_lock};
+ m_transaction_cond.wait(locker, [&locator, this] {
+ return m_transactions.count(locator) == 0;
+ });
+ auto result = m_transactions.insert(locator);
+ ceph_assert(result.second);
+}
+
+void TestMemCluster::transaction_finish(const ObjectLocator& locator) {
+ std::lock_guard locker{m_lock};
+ size_t count = m_transactions.erase(locator);
+ ceph_assert(count == 1);
+ m_transaction_cond.notify_all();
+}
+
+} // namespace librados
+
diff --git a/src/test/librados_test_stub/TestMemCluster.h b/src/test/librados_test_stub/TestMemCluster.h
new file mode 100644
index 000000000..2e80bff17
--- /dev/null
+++ b/src/test/librados_test_stub/TestMemCluster.h
@@ -0,0 +1,124 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_MEM_CLUSTER_H
+#define CEPH_TEST_MEM_CLUSTER_H
+
+#include "test/librados_test_stub/TestCluster.h"
+#include "include/buffer.h"
+#include "include/interval_set.h"
+#include "include/int_types.h"
+#include "common/ceph_mutex.h"
+#include "common/RefCountedObj.h"
+#include <boost/shared_ptr.hpp>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+namespace librados {
+
+class TestMemCluster : public TestCluster {
+public:
+ typedef std::map<std::string, bufferlist> OMap;
+ typedef std::map<ObjectLocator, OMap> FileOMaps;
+ typedef std::map<ObjectLocator, bufferlist> FileTMaps;
+ typedef std::map<std::string, bufferlist> XAttrs;
+ typedef std::map<ObjectLocator, XAttrs> FileXAttrs;
+ typedef std::set<ObjectHandler*> ObjectHandlers;
+ typedef std::map<ObjectLocator, ObjectHandlers> FileHandlers;
+
+ struct File {
+ File();
+ File(const File &rhs);
+
+ bufferlist data;
+ time_t mtime;
+ uint64_t objver;
+
+ uint64_t snap_id;
+ std::vector<uint64_t> snaps;
+ interval_set<uint64_t> snap_overlap;
+
+ bool exists;
+ ceph::shared_mutex lock =
+ ceph::make_shared_mutex("TestMemCluster::File::lock");
+ };
+ typedef boost::shared_ptr<File> SharedFile;
+
+ typedef std::list<SharedFile> FileSnapshots;
+ typedef std::map<ObjectLocator, FileSnapshots> Files;
+
+ typedef std::set<uint64_t> SnapSeqs;
+ struct Pool : public RefCountedObject {
+ Pool();
+
+ int64_t pool_id = 0;
+
+ SnapSeqs snap_seqs;
+ uint64_t snap_id = 1;
+
+ ceph::shared_mutex file_lock =
+ ceph::make_shared_mutex("TestMemCluster::Pool::file_lock");
+ Files files;
+ FileOMaps file_omaps;
+ FileTMaps file_tmaps;
+ FileXAttrs file_xattrs;
+ FileHandlers file_handlers;
+ };
+
+ TestMemCluster();
+ ~TestMemCluster() override;
+
+ TestRadosClient *create_rados_client(CephContext *cct) override;
+
+ int register_object_handler(int64_t pool_id, const ObjectLocator& locator,
+ ObjectHandler* object_handler) override;
+ void unregister_object_handler(int64_t pool_id, const ObjectLocator& locator,
+ ObjectHandler* object_handler) override;
+
+ int pool_create(const std::string &pool_name);
+ int pool_delete(const std::string &pool_name);
+ int pool_get_base_tier(int64_t pool_id, int64_t* base_tier);
+ int pool_list(std::list<std::pair<int64_t, std::string> >& v);
+ int64_t pool_lookup(const std::string &name);
+ int pool_reverse_lookup(int64_t id, std::string *name);
+
+ Pool *get_pool(int64_t pool_id);
+ Pool *get_pool(const std::string &pool_name);
+
+ void allocate_client(uint32_t *nonce, uint64_t *global_id);
+ void deallocate_client(uint32_t nonce);
+
+ bool is_blocklisted(uint32_t nonce) const;
+ void blocklist(uint32_t nonce);
+
+ void transaction_start(const ObjectLocator& locator);
+ void transaction_finish(const ObjectLocator& locator);
+
+private:
+
+ typedef std::map<std::string, Pool*> Pools;
+ typedef std::set<uint32_t> Blocklist;
+
+ mutable ceph::mutex m_lock =
+ ceph::make_mutex("TestMemCluster::m_lock");
+
+ Pools m_pools;
+ int64_t m_pool_id = 0;
+
+ uint32_t m_next_nonce;
+ uint64_t m_next_global_id = 1234;
+
+ Blocklist m_blocklist;
+
+ ceph::condition_variable m_transaction_cond;
+ std::set<ObjectLocator> m_transactions;
+
+ Pool *get_pool(const ceph::mutex& lock, int64_t pool_id);
+
+};
+
+} // namespace librados
+
+#endif // CEPH_TEST_MEM_CLUSTER_H
diff --git a/src/test/librados_test_stub/TestMemIoCtxImpl.cc b/src/test/librados_test_stub/TestMemIoCtxImpl.cc
new file mode 100644
index 000000000..77ea14366
--- /dev/null
+++ b/src/test/librados_test_stub/TestMemIoCtxImpl.cc
@@ -0,0 +1,924 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librados_test_stub/TestMemIoCtxImpl.h"
+#include "test/librados_test_stub/TestMemRadosClient.h"
+#include "common/Clock.h"
+#include "include/err.h"
+#include <functional>
+#include <boost/algorithm/string/predicate.hpp>
+#include <errno.h>
+#include <include/compat.h>
+
+#define dout_subsys ceph_subsys_rados
+#undef dout_prefix
+#define dout_prefix *_dout << "TestMemIoCtxImpl: " << this << " " << __func__ \
+ << ": " << oid << " "
+
+static void to_vector(const interval_set<uint64_t> &set,
+ std::vector<std::pair<uint64_t, uint64_t> > *vec) {
+ vec->clear();
+ for (interval_set<uint64_t>::const_iterator it = set.begin();
+ it != set.end(); ++it) {
+ vec->push_back(*it);
+ }
+}
+
+// see PrimaryLogPG::finish_extent_cmp()
+static int cmpext_compare(const bufferlist &bl, const bufferlist &read_bl) {
+ for (uint64_t idx = 0; idx < bl.length(); ++idx) {
+ char read_byte = (idx < read_bl.length() ? read_bl[idx] : 0);
+ if (bl[idx] != read_byte) {
+ return -MAX_ERRNO - idx;
+ }
+ }
+ return 0;
+}
+
+namespace librados {
+
+TestMemIoCtxImpl::TestMemIoCtxImpl() {
+}
+
+TestMemIoCtxImpl::TestMemIoCtxImpl(const TestMemIoCtxImpl& rhs)
+ : TestIoCtxImpl(rhs), m_client(rhs.m_client), m_pool(rhs.m_pool) {
+ m_pool->get();
+}
+
+TestMemIoCtxImpl::TestMemIoCtxImpl(TestMemRadosClient *client, int64_t pool_id,
+ const std::string& pool_name,
+ TestMemCluster::Pool *pool)
+ : TestIoCtxImpl(client, pool_id, pool_name), m_client(client),
+ m_pool(pool) {
+ m_pool->get();
+}
+
+TestMemIoCtxImpl::~TestMemIoCtxImpl() {
+ m_pool->put();
+}
+
+TestIoCtxImpl *TestMemIoCtxImpl::clone() {
+ return new TestMemIoCtxImpl(*this);
+}
+
+int TestMemIoCtxImpl::aio_remove(const std::string& oid, AioCompletionImpl *c, int flags) {
+ m_client->add_aio_operation(oid, true,
+ std::bind(&TestMemIoCtxImpl::remove, this, oid,
+ get_snap_context()),
+ c);
+ return 0;
+}
+
+int TestMemIoCtxImpl::append(const std::string& oid, const bufferlist &bl,
+ const SnapContext &snapc) {
+ if (get_snap_read() != CEPH_NOSNAP) {
+ return -EROFS;
+ } else if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ auto cct = m_client->cct();
+ ldout(cct, 20) << "length=" << bl.length() << ", snapc=" << snapc << dendl;
+
+ TestMemCluster::SharedFile file;
+ {
+ std::unique_lock l{m_pool->file_lock};
+ file = get_file(oid, true, CEPH_NOSNAP, snapc);
+ }
+
+ std::unique_lock l{file->lock};
+ auto off = file->data.length();
+ ensure_minimum_length(off + bl.length(), &file->data);
+ file->data.begin(off).copy_in(bl.length(), bl);
+ return 0;
+}
+
+int TestMemIoCtxImpl::assert_exists(const std::string &oid, uint64_t snap_id) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ std::shared_lock l{m_pool->file_lock};
+ TestMemCluster::SharedFile file = get_file(oid, false, snap_id, {});
+ if (file == NULL) {
+ return -ENOENT;
+ }
+ return 0;
+}
+
+int TestMemIoCtxImpl::assert_version(const std::string &oid, uint64_t ver) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ std::shared_lock l{m_pool->file_lock};
+ TestMemCluster::SharedFile file = get_file(oid, false, CEPH_NOSNAP, {});
+ if (file == NULL || !file->exists) {
+ return -ENOENT;
+ }
+ if (ver < file->objver) {
+ return -ERANGE;
+ }
+ if (ver > file->objver) {
+ return -EOVERFLOW;
+ }
+
+ return 0;
+}
+
+int TestMemIoCtxImpl::create(const std::string& oid, bool exclusive,
+ const SnapContext &snapc) {
+ if (get_snap_read() != CEPH_NOSNAP) {
+ return -EROFS;
+ } else if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ auto cct = m_client->cct();
+ ldout(cct, 20) << "snapc=" << snapc << dendl;
+
+ std::unique_lock l{m_pool->file_lock};
+ if (exclusive) {
+ TestMemCluster::SharedFile file = get_file(oid, false, CEPH_NOSNAP, {});
+ if (file != NULL && file->exists) {
+ return -EEXIST;
+ }
+ }
+
+ get_file(oid, true, CEPH_NOSNAP, snapc);
+ return 0;
+}
+
+int TestMemIoCtxImpl::list_snaps(const std::string& oid, snap_set_t *out_snaps) {
+ auto cct = m_client->cct();
+ ldout(cct, 20) << dendl;
+
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ out_snaps->seq = 0;
+ out_snaps->clones.clear();
+
+ std::shared_lock l{m_pool->file_lock};
+ TestMemCluster::Files::iterator it = m_pool->files.find(
+ {get_namespace(), oid});
+ if (it == m_pool->files.end()) {
+ return -ENOENT;
+ }
+
+ bool include_head = false;
+ TestMemCluster::FileSnapshots &file_snaps = it->second;
+ for (TestMemCluster::FileSnapshots::iterator s_it = file_snaps.begin();
+ s_it != file_snaps.end(); ++s_it) {
+ TestMemCluster::File &file = *s_it->get();
+
+ if (file_snaps.size() > 1) {
+ out_snaps->seq = file.snap_id;
+ TestMemCluster::FileSnapshots::iterator next_it(s_it);
+ ++next_it;
+ if (next_it == file_snaps.end()) {
+ include_head = true;
+ break;
+ }
+
+ ++out_snaps->seq;
+ if (!file.exists) {
+ continue;
+ }
+
+ // update the overlap with the next version's overlap metadata
+ TestMemCluster::File &next_file = *next_it->get();
+ interval_set<uint64_t> overlap;
+ if (next_file.exists) {
+ overlap = next_file.snap_overlap;
+ }
+
+ clone_info_t clone;
+ clone.cloneid = file.snap_id;
+ clone.snaps = file.snaps;
+ to_vector(overlap, &clone.overlap);
+ clone.size = file.data.length();
+ out_snaps->clones.push_back(clone);
+ }
+ }
+
+ if ((file_snaps.size() == 1 && file_snaps.back()->data.length() > 0) ||
+ include_head)
+ {
+ // Include the SNAP_HEAD
+ TestMemCluster::File &file = *file_snaps.back();
+ if (file.exists) {
+ std::shared_lock l2{file.lock};
+ if (out_snaps->seq == 0 && !include_head) {
+ out_snaps->seq = file.snap_id;
+ }
+ clone_info_t head_clone;
+ head_clone.cloneid = librados::SNAP_HEAD;
+ head_clone.size = file.data.length();
+ out_snaps->clones.push_back(head_clone);
+ }
+ }
+
+ ldout(cct, 20) << "seq=" << out_snaps->seq << ", "
+ << "clones=[";
+ bool first_clone = true;
+ for (auto& clone : out_snaps->clones) {
+ *_dout << "{"
+ << "cloneid=" << clone.cloneid << ", "
+ << "snaps=" << clone.snaps << ", "
+ << "overlap=" << clone.overlap << ", "
+ << "size=" << clone.size << "}";
+ if (!first_clone) {
+ *_dout << ", ";
+ } else {
+ first_clone = false;
+ }
+ }
+ *_dout << "]" << dendl;
+ return 0;
+
+}
+
+int TestMemIoCtxImpl::omap_get_vals2(const std::string& oid,
+ const std::string& start_after,
+ const std::string &filter_prefix,
+ uint64_t max_return,
+ std::map<std::string, bufferlist> *out_vals,
+ bool *pmore) {
+ if (out_vals == NULL) {
+ return -EINVAL;
+ } else if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ TestMemCluster::SharedFile file;
+ {
+ std::shared_lock l{m_pool->file_lock};
+ file = get_file(oid, false, CEPH_NOSNAP, {});
+ if (file == NULL) {
+ return -ENOENT;
+ }
+ }
+
+ out_vals->clear();
+
+ std::shared_lock l{file->lock};
+ TestMemCluster::FileOMaps::iterator o_it = m_pool->file_omaps.find(
+ {get_namespace(), oid});
+ if (o_it == m_pool->file_omaps.end()) {
+ if (pmore) {
+ *pmore = false;
+ }
+ return 0;
+ }
+
+ TestMemCluster::OMap &omap = o_it->second;
+ TestMemCluster::OMap::iterator it = omap.begin();
+ if (!start_after.empty()) {
+ it = omap.upper_bound(start_after);
+ }
+
+ while (it != omap.end() && max_return > 0) {
+ if (filter_prefix.empty() ||
+ boost::algorithm::starts_with(it->first, filter_prefix)) {
+ (*out_vals)[it->first] = it->second;
+ --max_return;
+ }
+ ++it;
+ }
+ if (pmore) {
+ *pmore = (it != omap.end());
+ }
+ return 0;
+}
+
+int TestMemIoCtxImpl::omap_get_vals(const std::string& oid,
+ const std::string& start_after,
+ const std::string &filter_prefix,
+ uint64_t max_return,
+ std::map<std::string, bufferlist> *out_vals) {
+ return omap_get_vals2(oid, start_after, filter_prefix, max_return, out_vals, nullptr);
+}
+
+int TestMemIoCtxImpl::omap_rm_keys(const std::string& oid,
+ const std::set<std::string>& keys) {
+ if (get_snap_read() != CEPH_NOSNAP) {
+ return -EROFS;
+ } else if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ TestMemCluster::SharedFile file;
+ {
+ std::unique_lock l{m_pool->file_lock};
+ file = get_file(oid, true, CEPH_NOSNAP, get_snap_context());
+ if (file == NULL) {
+ return -ENOENT;
+ }
+ }
+
+ std::unique_lock l{file->lock};
+ for (std::set<std::string>::iterator it = keys.begin();
+ it != keys.end(); ++it) {
+ m_pool->file_omaps[{get_namespace(), oid}].erase(*it);
+ }
+ return 0;
+}
+
+int TestMemIoCtxImpl::omap_set(const std::string& oid,
+ const std::map<std::string, bufferlist> &map) {
+ if (get_snap_read() != CEPH_NOSNAP) {
+ return -EROFS;
+ } else if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ TestMemCluster::SharedFile file;
+ {
+ std::unique_lock l{m_pool->file_lock};
+ file = get_file(oid, true, CEPH_NOSNAP, get_snap_context());
+ if (file == NULL) {
+ return -ENOENT;
+ }
+ }
+
+ std::unique_lock l{file->lock};
+ for (std::map<std::string, bufferlist>::const_iterator it = map.begin();
+ it != map.end(); ++it) {
+ bufferlist bl;
+ bl.append(it->second);
+ m_pool->file_omaps[{get_namespace(), oid}][it->first] = bl;
+ }
+
+ return 0;
+}
+
+int TestMemIoCtxImpl::read(const std::string& oid, size_t len, uint64_t off,
+ bufferlist *bl, uint64_t snap_id,
+ uint64_t* objver) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ TestMemCluster::SharedFile file;
+ {
+ std::shared_lock l{m_pool->file_lock};
+ file = get_file(oid, false, snap_id, {});
+ if (file == NULL) {
+ return -ENOENT;
+ }
+ }
+
+ std::shared_lock l{file->lock};
+ if (len == 0) {
+ len = file->data.length();
+ }
+ len = clip_io(off, len, file->data.length());
+ if (bl != NULL && len > 0) {
+ bufferlist bit;
+ bit.substr_of(file->data, off, len);
+ append_clone(bit, bl);
+ }
+ if (objver != nullptr) {
+ *objver = file->objver;
+ }
+ return len;
+}
+
+int TestMemIoCtxImpl::remove(const std::string& oid, const SnapContext &snapc) {
+ if (get_snap_read() != CEPH_NOSNAP) {
+ return -EROFS;
+ } else if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ auto cct = m_client->cct();
+ ldout(cct, 20) << "snapc=" << snapc << dendl;
+
+ std::unique_lock l{m_pool->file_lock};
+ TestMemCluster::SharedFile file = get_file(oid, false, CEPH_NOSNAP, snapc);
+ if (file == NULL) {
+ return -ENOENT;
+ }
+ file = get_file(oid, true, CEPH_NOSNAP, snapc);
+
+ {
+ std::unique_lock l2{file->lock};
+ file->exists = false;
+ }
+
+ TestCluster::ObjectLocator locator(get_namespace(), oid);
+ TestMemCluster::Files::iterator it = m_pool->files.find(locator);
+ ceph_assert(it != m_pool->files.end());
+
+ if (*it->second.rbegin() == file) {
+ TestMemCluster::ObjectHandlers object_handlers;
+ std::swap(object_handlers, m_pool->file_handlers[locator]);
+ m_pool->file_handlers.erase(locator);
+
+ for (auto object_handler : object_handlers) {
+ object_handler->handle_removed(m_client);
+ }
+ }
+
+ if (it->second.size() == 1) {
+ m_pool->files.erase(it);
+ m_pool->file_omaps.erase(locator);
+ }
+ return 0;
+}
+
+int TestMemIoCtxImpl::selfmanaged_snap_create(uint64_t *snapid) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ std::unique_lock l{m_pool->file_lock};
+ *snapid = ++m_pool->snap_id;
+ m_pool->snap_seqs.insert(*snapid);
+ return 0;
+}
+
+int TestMemIoCtxImpl::selfmanaged_snap_remove(uint64_t snapid) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ std::unique_lock l{m_pool->file_lock};
+ TestMemCluster::SnapSeqs::iterator it =
+ m_pool->snap_seqs.find(snapid);
+ if (it == m_pool->snap_seqs.end()) {
+ return -ENOENT;
+ }
+
+ // TODO clean up all file snapshots
+ m_pool->snap_seqs.erase(it);
+ return 0;
+}
+
+int TestMemIoCtxImpl::selfmanaged_snap_rollback(const std::string& oid,
+ uint64_t snapid) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ std::unique_lock l{m_pool->file_lock};
+
+ TestMemCluster::SharedFile file;
+ TestMemCluster::Files::iterator f_it = m_pool->files.find(
+ {get_namespace(), oid});
+ if (f_it == m_pool->files.end()) {
+ return 0;
+ }
+
+ TestMemCluster::FileSnapshots &snaps = f_it->second;
+ file = snaps.back();
+
+ size_t versions = 0;
+ for (TestMemCluster::FileSnapshots::reverse_iterator it = snaps.rbegin();
+ it != snaps.rend(); ++it) {
+ TestMemCluster::SharedFile file = *it;
+ if (file->snap_id < get_snap_read()) {
+ if (versions == 0) {
+ // already at the snapshot version
+ return 0;
+ } else if (file->snap_id == CEPH_NOSNAP) {
+ if (versions == 1) {
+ // delete it current HEAD, next one is correct version
+ snaps.erase(it.base());
+ } else {
+ // overwrite contents of current HEAD
+ file = TestMemCluster::SharedFile (new TestMemCluster::File(**it));
+ file->snap_id = CEPH_NOSNAP;
+ *it = file;
+ }
+ } else {
+ // create new head version
+ file = TestMemCluster::SharedFile (new TestMemCluster::File(**it));
+ file->snap_id = m_pool->snap_id;
+ snaps.push_back(file);
+ }
+ return 0;
+ }
+ ++versions;
+ }
+ return 0;
+}
+
+int TestMemIoCtxImpl::set_alloc_hint(const std::string& oid,
+ uint64_t expected_object_size,
+ uint64_t expected_write_size,
+ uint32_t flags,
+ const SnapContext &snapc) {
+ if (get_snap_read() != CEPH_NOSNAP) {
+ return -EROFS;
+ } else if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ {
+ std::unique_lock l{m_pool->file_lock};
+ get_file(oid, true, CEPH_NOSNAP, snapc);
+ }
+
+ return 0;
+}
+
+int TestMemIoCtxImpl::sparse_read(const std::string& oid, uint64_t off,
+ uint64_t len,
+ std::map<uint64_t,uint64_t> *m,
+ bufferlist *data_bl, uint64_t snap_id) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ // TODO verify correctness
+ TestMemCluster::SharedFile file;
+ {
+ std::shared_lock l{m_pool->file_lock};
+ file = get_file(oid, false, snap_id, {});
+ if (file == NULL) {
+ return -ENOENT;
+ }
+ }
+
+ std::shared_lock l{file->lock};
+ len = clip_io(off, len, file->data.length());
+ // TODO support sparse read
+ if (m != NULL) {
+ m->clear();
+ if (len > 0) {
+ (*m)[off] = len;
+ }
+ }
+ if (data_bl != NULL && len > 0) {
+ bufferlist bit;
+ bit.substr_of(file->data, off, len);
+ append_clone(bit, data_bl);
+ }
+ return len > 0 ? 1 : 0;
+}
+
+int TestMemIoCtxImpl::stat(const std::string& oid, uint64_t *psize,
+ time_t *pmtime) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ TestMemCluster::SharedFile file;
+ {
+ std::shared_lock l{m_pool->file_lock};
+ file = get_file(oid, false, CEPH_NOSNAP, {});
+ if (file == NULL) {
+ return -ENOENT;
+ }
+ }
+
+ std::shared_lock l{file->lock};
+ if (psize != NULL) {
+ *psize = file->data.length();
+ }
+ if (pmtime != NULL) {
+ *pmtime = file->mtime;
+ }
+ return 0;
+}
+
+int TestMemIoCtxImpl::truncate(const std::string& oid, uint64_t size,
+ const SnapContext &snapc) {
+ if (get_snap_read() != CEPH_NOSNAP) {
+ return -EROFS;
+ } else if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ auto cct = m_client->cct();
+ ldout(cct, 20) << "size=" << size << ", snapc=" << snapc << dendl;
+
+ TestMemCluster::SharedFile file;
+ {
+ std::unique_lock l{m_pool->file_lock};
+ file = get_file(oid, true, CEPH_NOSNAP, snapc);
+ }
+
+ std::unique_lock l{file->lock};
+ bufferlist bl(size);
+
+ interval_set<uint64_t> is;
+ if (file->data.length() > size) {
+ is.insert(size, file->data.length() - size);
+
+ bl.substr_of(file->data, 0, size);
+ file->data.swap(bl);
+ } else if (file->data.length() != size) {
+ if (size == 0) {
+ bl.clear();
+ } else {
+ is.insert(0, size);
+
+ bl.append_zero(size - file->data.length());
+ file->data.append(bl);
+ }
+ }
+ is.intersection_of(file->snap_overlap);
+ file->snap_overlap.subtract(is);
+ return 0;
+}
+
+int TestMemIoCtxImpl::write(const std::string& oid, bufferlist& bl, size_t len,
+ uint64_t off, const SnapContext &snapc) {
+ if (get_snap_read() != CEPH_NOSNAP) {
+ return -EROFS;
+ } else if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ auto cct = m_client->cct();
+ ldout(cct, 20) << "extent=" << off << "~" << len << ", snapc=" << snapc
+ << dendl;
+
+ TestMemCluster::SharedFile file;
+ {
+ std::unique_lock l{m_pool->file_lock};
+ file = get_file(oid, true, CEPH_NOSNAP, snapc);
+ }
+
+ std::unique_lock l{file->lock};
+ if (len > 0) {
+ interval_set<uint64_t> is;
+ is.insert(off, len);
+ is.intersection_of(file->snap_overlap);
+ file->snap_overlap.subtract(is);
+ }
+
+ ensure_minimum_length(off + len, &file->data);
+ file->data.begin(off).copy_in(len, bl);
+ return 0;
+}
+
+int TestMemIoCtxImpl::write_full(const std::string& oid, bufferlist& bl,
+ const SnapContext &snapc) {
+ if (get_snap_read() != CEPH_NOSNAP) {
+ return -EROFS;
+ } else if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ auto cct = m_client->cct();
+ ldout(cct, 20) << "length=" << bl.length() << ", snapc=" << snapc << dendl;
+
+ TestMemCluster::SharedFile file;
+ {
+ std::unique_lock l{m_pool->file_lock};
+ file = get_file(oid, true, CEPH_NOSNAP, snapc);
+ if (file == NULL) {
+ return -ENOENT;
+ }
+ }
+
+ std::unique_lock l{file->lock};
+ if (bl.length() > 0) {
+ interval_set<uint64_t> is;
+ is.insert(0, bl.length());
+ is.intersection_of(file->snap_overlap);
+ file->snap_overlap.subtract(is);
+ }
+
+ file->data.clear();
+ ensure_minimum_length(bl.length(), &file->data);
+ file->data.begin().copy_in(bl.length(), bl);
+ return 0;
+}
+
+int TestMemIoCtxImpl::writesame(const std::string& oid, bufferlist& bl,
+ size_t len, uint64_t off,
+ const SnapContext &snapc) {
+ if (get_snap_read() != CEPH_NOSNAP) {
+ return -EROFS;
+ } else if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ if (len == 0 || (len % bl.length())) {
+ return -EINVAL;
+ }
+
+ TestMemCluster::SharedFile file;
+ {
+ std::unique_lock l{m_pool->file_lock};
+ file = get_file(oid, true, CEPH_NOSNAP, snapc);
+ }
+
+ std::unique_lock l{file->lock};
+ if (len > 0) {
+ interval_set<uint64_t> is;
+ is.insert(off, len);
+ is.intersection_of(file->snap_overlap);
+ file->snap_overlap.subtract(is);
+ }
+
+ ensure_minimum_length(off + len, &file->data);
+ while (len > 0) {
+ file->data.begin(off).copy_in(bl.length(), bl);
+ off += bl.length();
+ len -= bl.length();
+ }
+ return 0;
+}
+
+int TestMemIoCtxImpl::cmpext(const std::string& oid, uint64_t off,
+ bufferlist& cmp_bl, uint64_t snap_id) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ bufferlist read_bl;
+ uint64_t len = cmp_bl.length();
+
+ TestMemCluster::SharedFile file;
+ {
+ std::shared_lock l{m_pool->file_lock};
+ file = get_file(oid, false, snap_id, {});
+ if (file == NULL) {
+ return cmpext_compare(cmp_bl, read_bl);
+ }
+ }
+
+ std::shared_lock l{file->lock};
+ if (off >= file->data.length()) {
+ len = 0;
+ } else if (off + len > file->data.length()) {
+ len = file->data.length() - off;
+ }
+ read_bl.substr_of(file->data, off, len);
+ return cmpext_compare(cmp_bl, read_bl);
+}
+
+int TestMemIoCtxImpl::xattr_get(const std::string& oid,
+ std::map<std::string, bufferlist>* attrset) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ TestMemCluster::SharedFile file;
+ std::shared_lock l{m_pool->file_lock};
+ TestMemCluster::FileXAttrs::iterator it = m_pool->file_xattrs.find(
+ {get_namespace(), oid});
+ if (it == m_pool->file_xattrs.end()) {
+ return -ENODATA;
+ }
+ *attrset = it->second;
+ return 0;
+}
+
+int TestMemIoCtxImpl::xattr_set(const std::string& oid, const std::string &name,
+ bufferlist& bl) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ std::unique_lock l{m_pool->file_lock};
+ m_pool->file_xattrs[{get_namespace(), oid}][name] = bl;
+ return 0;
+}
+
+int TestMemIoCtxImpl::zero(const std::string& oid, uint64_t off, uint64_t len,
+ const SnapContext &snapc) {
+ if (m_client->is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ auto cct = m_client->cct();
+ ldout(cct, 20) << "extent=" << off << "~" << len << ", snapc=" << snapc
+ << dendl;
+
+ bool truncate_redirect = false;
+ TestMemCluster::SharedFile file;
+ {
+ std::unique_lock l{m_pool->file_lock};
+ file = get_file(oid, false, CEPH_NOSNAP, snapc);
+ if (!file) {
+ return 0;
+ }
+ file = get_file(oid, true, CEPH_NOSNAP, snapc);
+
+ std::shared_lock l2{file->lock};
+ if (len > 0 && off + len >= file->data.length()) {
+ // Zero -> Truncate logic embedded in OSD
+ truncate_redirect = true;
+ }
+ }
+ if (truncate_redirect) {
+ return truncate(oid, off, snapc);
+ }
+
+ bufferlist bl;
+ bl.append_zero(len);
+ return write(oid, bl, len, off, snapc);
+}
+
+void TestMemIoCtxImpl::append_clone(bufferlist& src, bufferlist* dest) {
+ // deep-copy the src to ensure our memory-based mock RADOS data cannot
+ // be modified by callers
+ if (src.length() > 0) {
+ bufferlist::iterator iter = src.begin();
+ buffer::ptr ptr;
+ iter.copy_deep(src.length(), ptr);
+ dest->append(ptr);
+ }
+}
+
+size_t TestMemIoCtxImpl::clip_io(size_t off, size_t len, size_t bl_len) {
+ if (off >= bl_len) {
+ len = 0;
+ } else if (off + len > bl_len) {
+ len = bl_len - off;
+ }
+ return len;
+}
+
+void TestMemIoCtxImpl::ensure_minimum_length(size_t len, bufferlist *bl) {
+ if (len > bl->length()) {
+ bufferptr ptr(buffer::create(len - bl->length()));
+ ptr.zero();
+ bl->append(ptr);
+ }
+}
+
+TestMemCluster::SharedFile TestMemIoCtxImpl::get_file(
+ const std::string &oid, bool write, uint64_t snap_id,
+ const SnapContext &snapc) {
+ ceph_assert(ceph_mutex_is_locked(m_pool->file_lock) ||
+ ceph_mutex_is_wlocked(m_pool->file_lock));
+ ceph_assert(!write || ceph_mutex_is_wlocked(m_pool->file_lock));
+
+ TestMemCluster::SharedFile file;
+ TestMemCluster::Files::iterator it = m_pool->files.find(
+ {get_namespace(), oid});
+ if (it != m_pool->files.end()) {
+ file = it->second.back();
+ } else if (!write) {
+ return TestMemCluster::SharedFile();
+ }
+
+ if (write) {
+ bool new_version = false;
+ if (!file || !file->exists) {
+ file = TestMemCluster::SharedFile(new TestMemCluster::File());
+ new_version = true;
+ } else {
+ if (!snapc.snaps.empty() && file->snap_id < snapc.seq) {
+ for (std::vector<snapid_t>::const_reverse_iterator seq_it =
+ snapc.snaps.rbegin();
+ seq_it != snapc.snaps.rend(); ++seq_it) {
+ if (*seq_it > file->snap_id && *seq_it <= snapc.seq) {
+ file->snaps.push_back(*seq_it);
+ }
+ }
+
+ bufferlist prev_data = file->data;
+ file = TestMemCluster::SharedFile(
+ new TestMemCluster::File(*file));
+ file->data.clear();
+ append_clone(prev_data, &file->data);
+ if (prev_data.length() > 0) {
+ file->snap_overlap.insert(0, prev_data.length());
+ }
+ new_version = true;
+ }
+ }
+
+ if (new_version) {
+ file->snap_id = snapc.seq;
+ file->mtime = ceph_clock_now().sec();
+ m_pool->files[{get_namespace(), oid}].push_back(file);
+ }
+
+ file->objver++;
+ return file;
+ }
+
+ if (snap_id == CEPH_NOSNAP) {
+ if (!file->exists) {
+ ceph_assert(it->second.size() > 1);
+ return TestMemCluster::SharedFile();
+ }
+ return file;
+ }
+
+ TestMemCluster::FileSnapshots &snaps = it->second;
+ for (TestMemCluster::FileSnapshots::reverse_iterator it = snaps.rbegin();
+ it != snaps.rend(); ++it) {
+ TestMemCluster::SharedFile file = *it;
+ if (file->snap_id < snap_id) {
+ if (!file->exists) {
+ return TestMemCluster::SharedFile();
+ }
+ return file;
+ }
+ }
+ return TestMemCluster::SharedFile();
+}
+
+} // namespace librados
diff --git a/src/test/librados_test_stub/TestMemIoCtxImpl.h b/src/test/librados_test_stub/TestMemIoCtxImpl.h
new file mode 100644
index 000000000..4706f46d2
--- /dev/null
+++ b/src/test/librados_test_stub/TestMemIoCtxImpl.h
@@ -0,0 +1,104 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_MEM_IO_CTX_IMPL_H
+#define CEPH_TEST_MEM_IO_CTX_IMPL_H
+
+#include "test/librados_test_stub/TestIoCtxImpl.h"
+#include "test/librados_test_stub/TestMemCluster.h"
+
+namespace librados {
+
+class TestMemRadosClient;
+
+class TestMemIoCtxImpl : public TestIoCtxImpl {
+public:
+ TestMemIoCtxImpl();
+ TestMemIoCtxImpl(TestMemRadosClient *client, int64_t m_pool_id,
+ const std::string& pool_name,
+ TestMemCluster::Pool *pool);
+ ~TestMemIoCtxImpl() override;
+
+ TestIoCtxImpl *clone() override;
+
+ int aio_remove(const std::string& oid, AioCompletionImpl *c, int flags = 0) override;
+
+ int append(const std::string& oid, const bufferlist &bl,
+ const SnapContext &snapc) override;
+
+ int assert_exists(const std::string &oid, uint64_t snap_id) override;
+ int assert_version(const std::string &oid, uint64_t ver) override;
+
+ int create(const std::string& oid, bool exclusive,
+ const SnapContext &snapc) override;
+ int list_snaps(const std::string& o, snap_set_t *out_snaps) override;
+ int omap_get_vals(const std::string& oid,
+ const std::string& start_after,
+ const std::string &filter_prefix,
+ uint64_t max_return,
+ std::map<std::string, bufferlist> *out_vals) override;
+ int omap_get_vals2(const std::string& oid,
+ const std::string& start_after,
+ const std::string &filter_prefix,
+ uint64_t max_return,
+ std::map<std::string, bufferlist> *out_vals,
+ bool *pmore) override;
+ int omap_rm_keys(const std::string& oid,
+ const std::set<std::string>& keys) override;
+ int omap_set(const std::string& oid, const std::map<std::string,
+ bufferlist> &map) override;
+ int read(const std::string& oid, size_t len, uint64_t off,
+ bufferlist *bl, uint64_t snap_id, uint64_t* objver) override;
+ int remove(const std::string& oid, const SnapContext &snapc) override;
+ int selfmanaged_snap_create(uint64_t *snapid) override;
+ int selfmanaged_snap_remove(uint64_t snapid) override;
+ int selfmanaged_snap_rollback(const std::string& oid,
+ uint64_t snapid) override;
+ int set_alloc_hint(const std::string& oid, uint64_t expected_object_size,
+ uint64_t expected_write_size, uint32_t flags,
+ const SnapContext &snapc) override;
+ int sparse_read(const std::string& oid, uint64_t off, uint64_t len,
+ std::map<uint64_t,uint64_t> *m, bufferlist *data_bl,
+ uint64_t snap_id) override;
+ int stat(const std::string& oid, uint64_t *psize, time_t *pmtime) override;
+ int truncate(const std::string& oid, uint64_t size,
+ const SnapContext &snapc) override;
+ int write(const std::string& oid, bufferlist& bl, size_t len,
+ uint64_t off, const SnapContext &snapc) override;
+ int write_full(const std::string& oid, bufferlist& bl,
+ const SnapContext &snapc) override;
+ int writesame(const std::string& oid, bufferlist& bl, size_t len,
+ uint64_t off, const SnapContext &snapc) override;
+ int cmpext(const std::string& oid, uint64_t off, bufferlist& cmp_bl,
+ uint64_t snap_id) override;
+ int xattr_get(const std::string& oid,
+ std::map<std::string, bufferlist>* attrset) override;
+ int xattr_set(const std::string& oid, const std::string &name,
+ bufferlist& bl) override;
+ int zero(const std::string& oid, uint64_t off, uint64_t len,
+ const SnapContext &snapc) override;
+
+protected:
+ TestMemCluster::Pool *get_pool() {
+ return m_pool;
+ }
+
+private:
+ TestMemIoCtxImpl(const TestMemIoCtxImpl&);
+
+ TestMemRadosClient *m_client = nullptr;
+ TestMemCluster::Pool *m_pool = nullptr;
+
+ void append_clone(bufferlist& src, bufferlist* dest);
+ size_t clip_io(size_t off, size_t len, size_t bl_len);
+ void ensure_minimum_length(size_t len, bufferlist *bl);
+
+ TestMemCluster::SharedFile get_file(const std::string &oid, bool write,
+ uint64_t snap_id,
+ const SnapContext &snapc);
+
+};
+
+} // namespace librados
+
+#endif // CEPH_TEST_MEM_IO_CTX_IMPL_H
diff --git a/src/test/librados_test_stub/TestMemRadosClient.cc b/src/test/librados_test_stub/TestMemRadosClient.cc
new file mode 100644
index 000000000..37d45327c
--- /dev/null
+++ b/src/test/librados_test_stub/TestMemRadosClient.cc
@@ -0,0 +1,118 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librados_test_stub/TestMemRadosClient.h"
+#include "test/librados_test_stub/TestMemCluster.h"
+#include "test/librados_test_stub/TestMemIoCtxImpl.h"
+#include <errno.h>
+#include <sstream>
+
+namespace librados {
+
+TestMemRadosClient::TestMemRadosClient(CephContext *cct,
+ TestMemCluster *test_mem_cluster)
+ : TestRadosClient(cct, test_mem_cluster->get_watch_notify()),
+ m_mem_cluster(test_mem_cluster) {
+ m_mem_cluster->allocate_client(&m_nonce, &m_global_id);
+}
+
+TestMemRadosClient::~TestMemRadosClient() {
+ m_mem_cluster->deallocate_client(m_nonce);
+}
+
+TestIoCtxImpl *TestMemRadosClient::create_ioctx(int64_t pool_id,
+ const std::string &pool_name) {
+ return new TestMemIoCtxImpl(this, pool_id, pool_name,
+ m_mem_cluster->get_pool(pool_name));
+}
+
+void TestMemRadosClient::object_list(int64_t pool_id,
+ std::list<librados::TestRadosClient::Object> *list) {
+ list->clear();
+
+ auto pool = m_mem_cluster->get_pool(pool_id);
+ if (pool != nullptr) {
+ std::shared_lock file_locker{pool->file_lock};
+ for (auto &file_pair : pool->files) {
+ Object obj;
+ obj.oid = file_pair.first.name;
+ list->push_back(obj);
+ }
+ }
+}
+
+int TestMemRadosClient::pool_create(const std::string &pool_name) {
+ if (is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+ return m_mem_cluster->pool_create(pool_name);
+}
+
+int TestMemRadosClient::pool_delete(const std::string &pool_name) {
+ if (is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+ return m_mem_cluster->pool_delete(pool_name);
+}
+
+int TestMemRadosClient::pool_get_base_tier(int64_t pool_id, int64_t* base_tier) {
+ // TODO
+ *base_tier = pool_id;
+ return 0;
+}
+
+int TestMemRadosClient::pool_list(std::list<std::pair<int64_t, std::string> >& v) {
+ return m_mem_cluster->pool_list(v);
+}
+
+int64_t TestMemRadosClient::pool_lookup(const std::string &pool_name) {
+ return m_mem_cluster->pool_lookup(pool_name);
+}
+
+int TestMemRadosClient::pool_reverse_lookup(int64_t id, std::string *name) {
+ return m_mem_cluster->pool_reverse_lookup(id, name);
+}
+
+int TestMemRadosClient::watch_flush() {
+ get_watch_notify()->flush(this);
+ return 0;
+}
+
+bool TestMemRadosClient::is_blocklisted() const {
+ return m_mem_cluster->is_blocklisted(m_nonce);
+}
+
+int TestMemRadosClient::blocklist_add(const std::string& client_address,
+ uint32_t expire_seconds) {
+ if (is_blocklisted()) {
+ return -EBLOCKLISTED;
+ }
+
+ // extract the nonce to use as a unique key to the client
+ auto idx = client_address.find("/");
+ if (idx == std::string::npos || idx + 1 >= client_address.size()) {
+ return -EINVAL;
+ }
+
+ std::stringstream nonce_ss(client_address.substr(idx + 1));
+ uint32_t nonce;
+ nonce_ss >> nonce;
+ if (!nonce_ss) {
+ return -EINVAL;
+ }
+
+ m_mem_cluster->blocklist(nonce);
+ return 0;
+}
+
+void TestMemRadosClient::transaction_start(const std::string& nspace,
+ const std::string &oid) {
+ m_mem_cluster->transaction_start({nspace, oid});
+}
+
+void TestMemRadosClient::transaction_finish(const std::string& nspace,
+ const std::string &oid) {
+ m_mem_cluster->transaction_finish({nspace, oid});
+}
+
+} // namespace librados
diff --git a/src/test/librados_test_stub/TestMemRadosClient.h b/src/test/librados_test_stub/TestMemRadosClient.h
new file mode 100644
index 000000000..ecd6fedb0
--- /dev/null
+++ b/src/test/librados_test_stub/TestMemRadosClient.h
@@ -0,0 +1,88 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_MEM_RADOS_CLIENT_H
+#define CEPH_TEST_MEM_RADOS_CLIENT_H
+
+#include "test/librados_test_stub/TestRadosClient.h"
+#include "include/ceph_assert.h"
+#include <list>
+#include <string>
+
+namespace librados {
+
+class AioCompletionImpl;
+class TestMemCluster;
+
+class TestMemRadosClient : public TestRadosClient {
+public:
+ TestMemRadosClient(CephContext *cct, TestMemCluster *test_mem_cluster);
+ ~TestMemRadosClient() override;
+
+ TestIoCtxImpl *create_ioctx(int64_t pool_id,
+ const std::string &pool_name) override;
+
+ uint32_t get_nonce() override {
+ return m_nonce;
+ }
+ uint64_t get_instance_id() override {
+ return m_global_id;
+ }
+
+ int get_min_compatible_osd(int8_t* require_osd_release) override {
+ *require_osd_release = CEPH_RELEASE_OCTOPUS;
+ return 0;
+ }
+
+ int get_min_compatible_client(int8_t* min_compat_client,
+ int8_t* require_min_compat_client) override {
+ *min_compat_client = CEPH_RELEASE_MIMIC;
+ *require_min_compat_client = CEPH_RELEASE_MIMIC;
+ return 0;
+ }
+
+ void object_list(int64_t pool_id,
+ std::list<librados::TestRadosClient::Object> *list) override;
+
+ int service_daemon_register(const std::string& service,
+ const std::string& name,
+ const std::map<std::string,std::string>& metadata) override {
+ return 0;
+ }
+ int service_daemon_update_status(std::map<std::string,std::string>&& status) override {
+ return 0;
+ }
+
+ int pool_create(const std::string &pool_name) override;
+ int pool_delete(const std::string &pool_name) override;
+ int pool_get_base_tier(int64_t pool_id, int64_t* base_tier) override;
+ int pool_list(std::list<std::pair<int64_t, std::string> >& v) override;
+ int64_t pool_lookup(const std::string &name) override;
+ int pool_reverse_lookup(int64_t id, std::string *name) override;
+
+ int watch_flush() override;
+
+ bool is_blocklisted() const override;
+ int blocklist_add(const std::string& client_address,
+ uint32_t expire_seconds) override;
+protected:
+ TestMemCluster *get_mem_cluster() {
+ return m_mem_cluster;
+ }
+
+protected:
+ void transaction_start(const std::string& nspace,
+ const std::string &oid) override;
+ void transaction_finish(const std::string& nspace,
+ const std::string &oid) override;
+
+private:
+ TestMemCluster *m_mem_cluster;
+ uint32_t m_nonce;
+ uint64_t m_global_id;
+
+};
+
+} // namespace librados
+
+#endif // CEPH_TEST_MEM_RADOS_CLIENT_H
diff --git a/src/test/librados_test_stub/TestRadosClient.cc b/src/test/librados_test_stub/TestRadosClient.cc
new file mode 100644
index 000000000..1f49aba93
--- /dev/null
+++ b/src/test/librados_test_stub/TestRadosClient.cc
@@ -0,0 +1,311 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librados_test_stub/TestRadosClient.h"
+#include "test/librados_test_stub/TestIoCtxImpl.h"
+#include "librados/AioCompletionImpl.h"
+#include "include/ceph_assert.h"
+#include "common/ceph_json.h"
+#include "common/Finisher.h"
+#include "common/async/context_pool.h"
+#include <boost/lexical_cast.hpp>
+#include <boost/thread.hpp>
+#include <errno.h>
+
+#include <atomic>
+#include <functional>
+#include <sstream>
+
+static int get_concurrency() {
+ int concurrency = 0;
+ char *env = getenv("LIBRADOS_CONCURRENCY");
+ if (env != NULL) {
+ concurrency = atoi(env);
+ }
+ if (concurrency == 0) {
+ concurrency = boost::thread::thread::hardware_concurrency();
+ }
+ if (concurrency == 0) {
+ concurrency = 1;
+ }
+ return concurrency;
+}
+
+using namespace std::placeholders;
+
+namespace librados {
+
+namespace {
+
+const char *config_keys[] = {
+ "librados_thread_count",
+ NULL
+};
+
+} // anonymous namespace
+
+static void finish_aio_completion(AioCompletionImpl *c, int r) {
+ c->lock.lock();
+ c->complete = true;
+ c->rval = r;
+ c->lock.unlock();
+
+ rados_callback_t cb_complete = c->callback_complete;
+ void *cb_complete_arg = c->callback_complete_arg;
+ if (cb_complete) {
+ cb_complete(c, cb_complete_arg);
+ }
+
+ rados_callback_t cb_safe = c->callback_safe;
+ void *cb_safe_arg = c->callback_safe_arg;
+ if (cb_safe) {
+ cb_safe(c, cb_safe_arg);
+ }
+
+ c->lock.lock();
+ c->callback_complete = NULL;
+ c->callback_safe = NULL;
+ c->cond.notify_all();
+ c->put_unlock();
+}
+
+class AioFunctionContext : public Context {
+public:
+ AioFunctionContext(const TestRadosClient::AioFunction &callback,
+ Finisher *finisher, AioCompletionImpl *c)
+ : m_callback(callback), m_finisher(finisher), m_comp(c)
+ {
+ if (m_comp != NULL) {
+ m_comp->get();
+ }
+ }
+
+ void finish(int r) override {
+ int ret = m_callback();
+ if (m_comp != NULL) {
+ if (m_finisher != NULL) {
+ m_finisher->queue(new LambdaContext(std::bind(
+ &finish_aio_completion, m_comp, ret)));
+ } else {
+ finish_aio_completion(m_comp, ret);
+ }
+ }
+ }
+private:
+ TestRadosClient::AioFunction m_callback;
+ Finisher *m_finisher;
+ AioCompletionImpl *m_comp;
+};
+
+TestRadosClient::TestRadosClient(CephContext *cct,
+ TestWatchNotify *watch_notify)
+ : m_cct(cct->get()), m_watch_notify(watch_notify),
+ m_aio_finisher(new Finisher(m_cct)),
+ m_io_context_pool(std::make_unique<ceph::async::io_context_pool>())
+{
+ get();
+
+ // simulate multiple OSDs
+ int concurrency = get_concurrency();
+ for (int i = 0; i < concurrency; ++i) {
+ m_finishers.push_back(new Finisher(m_cct));
+ m_finishers.back()->start();
+ }
+
+ // replicate AIO callback processing
+ m_aio_finisher->start();
+
+ // replicate neorados callback processing
+ m_cct->_conf.add_observer(this);
+ m_io_context_pool->start(m_cct->_conf.get_val<uint64_t>(
+ "librados_thread_count"));
+}
+
+TestRadosClient::~TestRadosClient() {
+ flush_aio_operations();
+
+ for (size_t i = 0; i < m_finishers.size(); ++i) {
+ m_finishers[i]->stop();
+ delete m_finishers[i];
+ }
+ m_aio_finisher->stop();
+ delete m_aio_finisher;
+
+ m_cct->_conf.remove_observer(this);
+ m_io_context_pool->stop();
+
+ m_cct->put();
+ m_cct = NULL;
+}
+
+boost::asio::io_context& TestRadosClient::get_io_context() {
+ return m_io_context_pool->get_io_context();
+}
+
+const char** TestRadosClient::get_tracked_conf_keys() const {
+ return config_keys;
+}
+
+void TestRadosClient::handle_conf_change(
+ const ConfigProxy& conf, const std::set<std::string> &changed) {
+ if (changed.count("librados_thread_count")) {
+ m_io_context_pool->stop();
+ m_io_context_pool->start(conf.get_val<std::uint64_t>(
+ "librados_thread_count"));
+ }
+}
+
+void TestRadosClient::get() {
+ m_refcount++;
+}
+
+void TestRadosClient::put() {
+ if (--m_refcount == 0) {
+ shutdown();
+ delete this;
+ }
+}
+
+CephContext *TestRadosClient::cct() {
+ return m_cct;
+}
+
+int TestRadosClient::connect() {
+ return 0;
+}
+
+void TestRadosClient::shutdown() {
+}
+
+int TestRadosClient::wait_for_latest_osdmap() {
+ return 0;
+}
+
+int TestRadosClient::mon_command(const std::vector<std::string>& cmd,
+ const bufferlist &inbl,
+ bufferlist *outbl, std::string *outs) {
+ for (std::vector<std::string>::const_iterator it = cmd.begin();
+ it != cmd.end(); ++it) {
+ JSONParser parser;
+ if (!parser.parse(it->c_str(), it->length())) {
+ return -EINVAL;
+ }
+
+ JSONObjIter j_it = parser.find("prefix");
+ if (j_it.end()) {
+ return -EINVAL;
+ }
+
+ if ((*j_it)->get_data() == "osd tier add") {
+ return 0;
+ } else if ((*j_it)->get_data() == "osd tier cache-mode") {
+ return 0;
+ } else if ((*j_it)->get_data() == "osd tier set-overlay") {
+ return 0;
+ } else if ((*j_it)->get_data() == "osd tier remove-overlay") {
+ return 0;
+ } else if ((*j_it)->get_data() == "osd tier remove") {
+ return 0;
+ } else if ((*j_it)->get_data() == "config-key rm") {
+ return 0;
+ } else if ((*j_it)->get_data() == "config set") {
+ return 0;
+ } else if ((*j_it)->get_data() == "df") {
+ std::stringstream str;
+ str << R"({"pools": [)";
+
+ std::list<std::pair<int64_t, std::string>> pools;
+ pool_list(pools);
+ for (auto& pool : pools) {
+ if (pools.begin()->first != pool.first) {
+ str << ",";
+ }
+ str << R"({"name": ")" << pool.second << R"(", "stats": )"
+ << R"({"percent_used": 1.0, "bytes_used": 0, "max_avail": 0}})";
+ }
+
+ str << "]}";
+ outbl->append(str.str());
+ return 0;
+ } else if ((*j_it)->get_data() == "osd blocklist") {
+ auto op_it = parser.find("blocklistop");
+ if (!op_it.end() && (*op_it)->get_data() == "add") {
+ uint32_t expire = 0;
+ auto expire_it = parser.find("expire");
+ if (!expire_it.end()) {
+ expire = boost::lexical_cast<uint32_t>((*expire_it)->get_data());
+ }
+
+ auto addr_it = parser.find("addr");
+ return blocklist_add((*addr_it)->get_data(), expire);
+ }
+ }
+ }
+ return -ENOSYS;
+}
+
+void TestRadosClient::add_aio_operation(const std::string& oid,
+ bool queue_callback,
+ const AioFunction &aio_function,
+ AioCompletionImpl *c) {
+ AioFunctionContext *ctx = new AioFunctionContext(
+ aio_function, queue_callback ? m_aio_finisher : NULL, c);
+ get_finisher(oid)->queue(ctx);
+}
+
+struct WaitForFlush {
+ int flushed() {
+ if (--count == 0) {
+ aio_finisher->queue(new LambdaContext(std::bind(
+ &finish_aio_completion, c, 0)));
+ delete this;
+ }
+ return 0;
+ }
+
+ std::atomic<int64_t> count = { 0 };
+ Finisher *aio_finisher;
+ AioCompletionImpl *c;
+};
+
+void TestRadosClient::flush_aio_operations() {
+ AioCompletionImpl *comp = new AioCompletionImpl();
+ flush_aio_operations(comp);
+ comp->wait_for_complete();
+ comp->put();
+}
+
+void TestRadosClient::flush_aio_operations(AioCompletionImpl *c) {
+ c->get();
+
+ WaitForFlush *wait_for_flush = new WaitForFlush();
+ wait_for_flush->count = m_finishers.size();
+ wait_for_flush->aio_finisher = m_aio_finisher;
+ wait_for_flush->c = c;
+
+ for (size_t i = 0; i < m_finishers.size(); ++i) {
+ AioFunctionContext *ctx = new AioFunctionContext(
+ std::bind(&WaitForFlush::flushed, wait_for_flush),
+ nullptr, nullptr);
+ m_finishers[i]->queue(ctx);
+ }
+}
+
+int TestRadosClient::aio_watch_flush(AioCompletionImpl *c) {
+ c->get();
+ Context *ctx = new LambdaContext(std::bind(
+ &TestRadosClient::finish_aio_completion, this, c, std::placeholders::_1));
+ get_watch_notify()->aio_flush(this, ctx);
+ return 0;
+}
+
+void TestRadosClient::finish_aio_completion(AioCompletionImpl *c, int r) {
+ librados::finish_aio_completion(c, r);
+}
+
+Finisher *TestRadosClient::get_finisher(const std::string &oid) {
+ std::size_t h = m_hash(oid);
+ return m_finishers[h % m_finishers.size()];
+}
+
+} // namespace librados
diff --git a/src/test/librados_test_stub/TestRadosClient.h b/src/test/librados_test_stub/TestRadosClient.h
new file mode 100644
index 000000000..e7f8d0751
--- /dev/null
+++ b/src/test/librados_test_stub/TestRadosClient.h
@@ -0,0 +1,162 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_RADOS_CLIENT_H
+#define CEPH_TEST_RADOS_CLIENT_H
+
+#include <map>
+#include <memory>
+#include <list>
+#include <string>
+#include <vector>
+#include <atomic>
+
+#include <boost/function.hpp>
+#include <boost/functional/hash.hpp>
+
+#include "include/rados/librados.hpp"
+#include "common/config.h"
+#include "common/config_obs.h"
+#include "include/buffer_fwd.h"
+#include "test/librados_test_stub/TestWatchNotify.h"
+
+class Finisher;
+
+namespace boost { namespace asio { struct io_context; }}
+namespace ceph { namespace async { struct io_context_pool; }}
+
+namespace librados {
+
+class TestIoCtxImpl;
+
+class TestRadosClient : public md_config_obs_t {
+public:
+
+ static void Deallocate(librados::TestRadosClient* client)
+ {
+ client->put();
+ }
+
+ typedef boost::function<int()> AioFunction;
+
+ struct Object {
+ std::string oid;
+ std::string locator;
+ std::string nspace;
+ };
+
+ class Transaction {
+ public:
+ Transaction(TestRadosClient *rados_client, const std::string& nspace,
+ const std::string &oid)
+ : rados_client(rados_client), nspace(nspace), oid(oid) {
+ rados_client->transaction_start(nspace, oid);
+ }
+ ~Transaction() {
+ rados_client->transaction_finish(nspace, oid);
+ }
+ private:
+ TestRadosClient *rados_client;
+ std::string nspace;
+ std::string oid;
+ };
+
+ TestRadosClient(CephContext *cct, TestWatchNotify *watch_notify);
+
+ void get();
+ void put();
+
+ virtual CephContext *cct();
+
+ virtual uint32_t get_nonce() = 0;
+ virtual uint64_t get_instance_id() = 0;
+
+ virtual int get_min_compatible_osd(int8_t* require_osd_release) = 0;
+ virtual int get_min_compatible_client(int8_t* min_compat_client,
+ int8_t* require_min_compat_client) = 0;
+
+ virtual int connect();
+ virtual void shutdown();
+ virtual int wait_for_latest_osdmap();
+
+ virtual TestIoCtxImpl *create_ioctx(int64_t pool_id,
+ const std::string &pool_name) = 0;
+
+ virtual int mon_command(const std::vector<std::string>& cmd,
+ const bufferlist &inbl,
+ bufferlist *outbl, std::string *outs);
+
+ virtual void object_list(int64_t pool_id,
+ std::list<librados::TestRadosClient::Object> *list) = 0;
+
+ virtual int service_daemon_register(const std::string& service,
+ const std::string& name,
+ const std::map<std::string,std::string>& metadata) = 0;
+ virtual int service_daemon_update_status(std::map<std::string,std::string>&& status) = 0;
+
+ virtual int pool_create(const std::string &pool_name) = 0;
+ virtual int pool_delete(const std::string &pool_name) = 0;
+ virtual int pool_get_base_tier(int64_t pool_id, int64_t* base_tier) = 0;
+ virtual int pool_list(std::list<std::pair<int64_t, std::string> >& v) = 0;
+ virtual int64_t pool_lookup(const std::string &name) = 0;
+ virtual int pool_reverse_lookup(int64_t id, std::string *name) = 0;
+
+ virtual int aio_watch_flush(AioCompletionImpl *c);
+ virtual int watch_flush() = 0;
+
+ virtual bool is_blocklisted() const = 0;
+ virtual int blocklist_add(const std::string& client_address,
+ uint32_t expire_seconds) = 0;
+
+ virtual int wait_for_latest_osd_map() {
+ return 0;
+ }
+
+ Finisher *get_aio_finisher() {
+ return m_aio_finisher;
+ }
+ TestWatchNotify *get_watch_notify() {
+ return m_watch_notify;
+ }
+
+ void add_aio_operation(const std::string& oid, bool queue_callback,
+ const AioFunction &aio_function, AioCompletionImpl *c);
+ void flush_aio_operations();
+ void flush_aio_operations(AioCompletionImpl *c);
+
+ void finish_aio_completion(AioCompletionImpl *c, int r);
+
+ boost::asio::io_context& get_io_context();
+
+protected:
+ virtual ~TestRadosClient();
+
+ virtual void transaction_start(const std::string& nspace,
+ const std::string &oid) = 0;
+ virtual void transaction_finish(const std::string& nspace,
+ const std::string &oid) = 0;
+
+ const char** get_tracked_conf_keys() const override;
+ void handle_conf_change(const ConfigProxy& conf,
+ const std::set<std::string> &changed) override;
+
+private:
+ struct IOContextPool;
+
+ CephContext *m_cct;
+ std::atomic<uint64_t> m_refcount = { 0 };
+
+ TestWatchNotify *m_watch_notify;
+
+ Finisher *get_finisher(const std::string& oid);
+
+ Finisher *m_aio_finisher;
+ std::vector<Finisher *> m_finishers;
+ boost::hash<std::string> m_hash;
+
+ std::unique_ptr<ceph::async::io_context_pool> m_io_context_pool;
+};
+
+} // namespace librados
+
+#endif // CEPH_TEST_RADOS_CLIENT_H
diff --git a/src/test/librados_test_stub/TestWatchNotify.cc b/src/test/librados_test_stub/TestWatchNotify.cc
new file mode 100644
index 000000000..93875182c
--- /dev/null
+++ b/src/test/librados_test_stub/TestWatchNotify.cc
@@ -0,0 +1,459 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librados_test_stub/TestWatchNotify.h"
+#include "include/Context.h"
+#include "common/Cond.h"
+#include "include/stringify.h"
+#include "common/Finisher.h"
+#include "test/librados_test_stub/TestCluster.h"
+#include "test/librados_test_stub/TestRadosClient.h"
+#include <boost/bind/bind.hpp>
+#include <boost/function.hpp>
+#include "include/ceph_assert.h"
+
+#define dout_subsys ceph_subsys_rados
+#undef dout_prefix
+#define dout_prefix *_dout << "TestWatchNotify::" << __func__ << ": "
+
+namespace librados {
+
+std::ostream& operator<<(std::ostream& out,
+ const TestWatchNotify::WatcherID &watcher_id) {
+ out << "(" << watcher_id.first << "," << watcher_id.second << ")";
+ return out;
+}
+
+struct TestWatchNotify::ObjectHandler : public TestCluster::ObjectHandler {
+ TestWatchNotify* test_watch_notify;
+ int64_t pool_id;
+ std::string nspace;
+ std::string oid;
+
+ ObjectHandler(TestWatchNotify* test_watch_notify, int64_t pool_id,
+ const std::string& nspace, const std::string& oid)
+ : test_watch_notify(test_watch_notify), pool_id(pool_id),
+ nspace(nspace), oid(oid) {
+ }
+
+ void handle_removed(TestRadosClient* test_rados_client) override {
+ // copy member variables since this object might be deleted
+ auto _test_watch_notify = test_watch_notify;
+ auto _pool_id = pool_id;
+ auto _nspace = nspace;
+ auto _oid = oid;
+ auto ctx = new LambdaContext([_test_watch_notify, _pool_id, _nspace, _oid](int r) {
+ _test_watch_notify->handle_object_removed(_pool_id, _nspace, _oid);
+ });
+ test_rados_client->get_aio_finisher()->queue(ctx);
+ }
+};
+
+TestWatchNotify::TestWatchNotify(TestCluster* test_cluster)
+ : m_test_cluster(test_cluster) {
+}
+
+void TestWatchNotify::flush(TestRadosClient *rados_client) {
+ CephContext *cct = rados_client->cct();
+
+ ldout(cct, 20) << "enter" << dendl;
+ // block until we know no additional async notify callbacks will occur
+ C_SaferCond ctx;
+ m_async_op_tracker.wait_for_ops(&ctx);
+ ctx.wait();
+}
+
+int TestWatchNotify::list_watchers(int64_t pool_id, const std::string& nspace,
+ const std::string& o,
+ std::list<obj_watch_t> *out_watchers) {
+ std::lock_guard lock{m_lock};
+ SharedWatcher watcher = get_watcher(pool_id, nspace, o);
+ if (!watcher) {
+ return -ENOENT;
+ }
+
+ out_watchers->clear();
+ for (TestWatchNotify::WatchHandles::iterator it =
+ watcher->watch_handles.begin();
+ it != watcher->watch_handles.end(); ++it) {
+ obj_watch_t obj;
+ strncpy(obj.addr, it->second.addr.c_str(), sizeof(obj.addr) - 1);
+ obj.addr[sizeof(obj.addr) - 1] = '\0';
+ obj.watcher_id = static_cast<int64_t>(it->second.gid);
+ obj.cookie = it->second.handle;
+ obj.timeout_seconds = 30;
+ out_watchers->push_back(obj);
+ }
+ return 0;
+}
+
+void TestWatchNotify::aio_flush(TestRadosClient *rados_client,
+ Context *on_finish) {
+ rados_client->get_aio_finisher()->queue(on_finish);
+}
+
+int TestWatchNotify::watch(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string& o,
+ uint64_t gid, uint64_t *handle,
+ librados::WatchCtx *ctx, librados::WatchCtx2 *ctx2) {
+ C_SaferCond cond;
+ aio_watch(rados_client, pool_id, nspace, o, gid, handle, ctx, ctx2, &cond);
+ return cond.wait();
+}
+
+void TestWatchNotify::aio_watch(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string& o,
+ uint64_t gid, uint64_t *handle,
+ librados::WatchCtx *watch_ctx,
+ librados::WatchCtx2 *watch_ctx2,
+ Context *on_finish) {
+ auto ctx = new LambdaContext([=, this](int) {
+ execute_watch(rados_client, pool_id, nspace, o, gid, handle, watch_ctx,
+ watch_ctx2, on_finish);
+ });
+ rados_client->get_aio_finisher()->queue(ctx);
+}
+
+int TestWatchNotify::unwatch(TestRadosClient *rados_client,
+ uint64_t handle) {
+ C_SaferCond ctx;
+ aio_unwatch(rados_client, handle, &ctx);
+ return ctx.wait();
+}
+
+void TestWatchNotify::aio_unwatch(TestRadosClient *rados_client,
+ uint64_t handle, Context *on_finish) {
+ auto ctx = new LambdaContext([this, rados_client, handle, on_finish](int) {
+ execute_unwatch(rados_client, handle, on_finish);
+ });
+ rados_client->get_aio_finisher()->queue(ctx);
+}
+
+void TestWatchNotify::aio_notify(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace,
+ const std::string& oid, const bufferlist& bl,
+ uint64_t timeout_ms, bufferlist *pbl,
+ Context *on_notify) {
+ auto ctx = new LambdaContext([=, this](int) {
+ execute_notify(rados_client, pool_id, nspace, oid, bl, pbl, on_notify);
+ });
+ rados_client->get_aio_finisher()->queue(ctx);
+}
+
+int TestWatchNotify::notify(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string& oid,
+ bufferlist& bl, uint64_t timeout_ms,
+ bufferlist *pbl) {
+ C_SaferCond cond;
+ aio_notify(rados_client, pool_id, nspace, oid, bl, timeout_ms, pbl, &cond);
+ return cond.wait();
+}
+
+void TestWatchNotify::notify_ack(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace,
+ const std::string& o, uint64_t notify_id,
+ uint64_t handle, uint64_t gid,
+ bufferlist& bl) {
+ CephContext *cct = rados_client->cct();
+ ldout(cct, 20) << "notify_id=" << notify_id << ", handle=" << handle
+ << ", gid=" << gid << dendl;
+ std::lock_guard lock{m_lock};
+ WatcherID watcher_id = std::make_pair(gid, handle);
+ ack_notify(rados_client, pool_id, nspace, o, notify_id, watcher_id, bl);
+ finish_notify(rados_client, pool_id, nspace, o, notify_id);
+}
+
+void TestWatchNotify::execute_watch(TestRadosClient *rados_client,
+ int64_t pool_id, const std::string& nspace,
+ const std::string& o, uint64_t gid,
+ uint64_t *handle, librados::WatchCtx *ctx,
+ librados::WatchCtx2 *ctx2,
+ Context* on_finish) {
+ CephContext *cct = rados_client->cct();
+
+ m_lock.lock();
+ SharedWatcher watcher = get_watcher(pool_id, nspace, o);
+ if (!watcher) {
+ m_lock.unlock();
+ on_finish->complete(-ENOENT);
+ return;
+ }
+
+ WatchHandle watch_handle;
+ watch_handle.rados_client = rados_client;
+ watch_handle.addr = "127.0.0.1:0/" + stringify(rados_client->get_nonce());
+ watch_handle.nonce = rados_client->get_nonce();
+ watch_handle.gid = gid;
+ watch_handle.handle = ++m_handle;
+ watch_handle.watch_ctx = ctx;
+ watch_handle.watch_ctx2 = ctx2;
+ watcher->watch_handles[watch_handle.handle] = watch_handle;
+
+ *handle = watch_handle.handle;
+
+ ldout(cct, 20) << "oid=" << o << ", gid=" << gid << ": handle=" << *handle
+ << dendl;
+ m_lock.unlock();
+
+ on_finish->complete(0);
+}
+
+void TestWatchNotify::execute_unwatch(TestRadosClient *rados_client,
+ uint64_t handle, Context* on_finish) {
+ CephContext *cct = rados_client->cct();
+
+ ldout(cct, 20) << "handle=" << handle << dendl;
+ {
+ std::lock_guard locker{m_lock};
+ for (FileWatchers::iterator it = m_file_watchers.begin();
+ it != m_file_watchers.end(); ++it) {
+ SharedWatcher watcher = it->second;
+
+ WatchHandles::iterator w_it = watcher->watch_handles.find(handle);
+ if (w_it != watcher->watch_handles.end()) {
+ watcher->watch_handles.erase(w_it);
+ maybe_remove_watcher(watcher);
+ break;
+ }
+ }
+ }
+ on_finish->complete(0);
+}
+
+TestWatchNotify::SharedWatcher TestWatchNotify::get_watcher(
+ int64_t pool_id, const std::string& nspace, const std::string& oid) {
+ ceph_assert(ceph_mutex_is_locked(m_lock));
+
+ auto it = m_file_watchers.find({pool_id, nspace, oid});
+ if (it == m_file_watchers.end()) {
+ SharedWatcher watcher(new Watcher(pool_id, nspace, oid));
+ watcher->object_handler.reset(new ObjectHandler(
+ this, pool_id, nspace, oid));
+ int r = m_test_cluster->register_object_handler(
+ pool_id, {nspace, oid}, watcher->object_handler.get());
+ if (r < 0) {
+ // object doesn't exist
+ return SharedWatcher();
+ }
+ m_file_watchers[{pool_id, nspace, oid}] = watcher;
+ return watcher;
+ }
+
+ return it->second;
+}
+
+void TestWatchNotify::maybe_remove_watcher(SharedWatcher watcher) {
+ ceph_assert(ceph_mutex_is_locked(m_lock));
+
+ // TODO
+ if (watcher->watch_handles.empty() && watcher->notify_handles.empty()) {
+ auto pool_id = watcher->pool_id;
+ auto& nspace = watcher->nspace;
+ auto& oid = watcher->oid;
+ if (watcher->object_handler) {
+ m_test_cluster->unregister_object_handler(pool_id, {nspace, oid},
+ watcher->object_handler.get());
+ watcher->object_handler.reset();
+ }
+
+ m_file_watchers.erase({pool_id, nspace, oid});
+ }
+}
+
+void TestWatchNotify::execute_notify(TestRadosClient *rados_client,
+ int64_t pool_id, const std::string& nspace,
+ const std::string &oid,
+ const bufferlist &bl, bufferlist *pbl,
+ Context *on_notify) {
+ CephContext *cct = rados_client->cct();
+
+ m_lock.lock();
+ uint64_t notify_id = ++m_notify_id;
+
+ SharedWatcher watcher = get_watcher(pool_id, nspace, oid);
+ if (!watcher) {
+ ldout(cct, 1) << "oid=" << oid << ": not found" << dendl;
+ m_lock.unlock();
+ on_notify->complete(-ENOENT);
+ return;
+ }
+
+ ldout(cct, 20) << "oid=" << oid << ": notify_id=" << notify_id << dendl;
+
+ SharedNotifyHandle notify_handle(new NotifyHandle());
+ notify_handle->rados_client = rados_client;
+ notify_handle->pbl = pbl;
+ notify_handle->on_notify = on_notify;
+
+ WatchHandles &watch_handles = watcher->watch_handles;
+ for (auto &watch_handle_pair : watch_handles) {
+ WatchHandle &watch_handle = watch_handle_pair.second;
+ notify_handle->pending_watcher_ids.insert(std::make_pair(
+ watch_handle.gid, watch_handle.handle));
+
+ m_async_op_tracker.start_op();
+ uint64_t notifier_id = rados_client->get_instance_id();
+ watch_handle.rados_client->get_aio_finisher()->queue(new LambdaContext(
+ [this, pool_id, nspace, oid, bl, notify_id, watch_handle, notifier_id](int r) {
+ bufferlist notify_bl;
+ notify_bl.append(bl);
+
+ if (watch_handle.watch_ctx2 != NULL) {
+ watch_handle.watch_ctx2->handle_notify(notify_id,
+ watch_handle.handle,
+ notifier_id, notify_bl);
+ } else if (watch_handle.watch_ctx != NULL) {
+ watch_handle.watch_ctx->notify(0, 0, notify_bl);
+
+ // auto ack old-style watch/notify clients
+ ack_notify(watch_handle.rados_client, pool_id, nspace, oid, notify_id,
+ {watch_handle.gid, watch_handle.handle}, bufferlist());
+ }
+
+ m_async_op_tracker.finish_op();
+ }));
+ }
+ watcher->notify_handles[notify_id] = notify_handle;
+
+ finish_notify(rados_client, pool_id, nspace, oid, notify_id);
+ m_lock.unlock();
+}
+
+void TestWatchNotify::ack_notify(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace,
+ const std::string &oid, uint64_t notify_id,
+ const WatcherID &watcher_id,
+ const bufferlist &bl) {
+ CephContext *cct = rados_client->cct();
+
+ ceph_assert(ceph_mutex_is_locked(m_lock));
+ SharedWatcher watcher = get_watcher(pool_id, nspace, oid);
+ if (!watcher) {
+ ldout(cct, 1) << "oid=" << oid << ": not found" << dendl;
+ return;
+ }
+
+ NotifyHandles::iterator it = watcher->notify_handles.find(notify_id);
+ if (it == watcher->notify_handles.end()) {
+ ldout(cct, 1) << "oid=" << oid << ", notify_id=" << notify_id
+ << ", WatcherID=" << watcher_id << ": not found" << dendl;
+ return;
+ }
+
+ ldout(cct, 20) << "oid=" << oid << ", notify_id=" << notify_id
+ << ", WatcherID=" << watcher_id << dendl;
+
+ bufferlist response;
+ response.append(bl);
+
+ SharedNotifyHandle notify_handle = it->second;
+ notify_handle->notify_responses[watcher_id] = response;
+ notify_handle->pending_watcher_ids.erase(watcher_id);
+}
+
+void TestWatchNotify::finish_notify(TestRadosClient *rados_client,
+ int64_t pool_id, const std::string& nspace,
+ const std::string &oid,
+ uint64_t notify_id) {
+ CephContext *cct = rados_client->cct();
+
+ ldout(cct, 20) << "oid=" << oid << ", notify_id=" << notify_id << dendl;
+
+ ceph_assert(ceph_mutex_is_locked(m_lock));
+ SharedWatcher watcher = get_watcher(pool_id, nspace, oid);
+ if (!watcher) {
+ ldout(cct, 1) << "oid=" << oid << ": not found" << dendl;
+ return;
+ }
+
+ NotifyHandles::iterator it = watcher->notify_handles.find(notify_id);
+ if (it == watcher->notify_handles.end()) {
+ ldout(cct, 1) << "oid=" << oid << ", notify_id=" << notify_id
+ << ": not found" << dendl;
+ return;
+ }
+
+ SharedNotifyHandle notify_handle = it->second;
+ if (!notify_handle->pending_watcher_ids.empty()) {
+ ldout(cct, 10) << "oid=" << oid << ", notify_id=" << notify_id
+ << ": pending watchers, returning" << dendl;
+ return;
+ }
+
+ ldout(cct, 20) << "oid=" << oid << ", notify_id=" << notify_id
+ << ": completing" << dendl;
+
+ if (notify_handle->pbl != NULL) {
+ encode(notify_handle->notify_responses, *notify_handle->pbl);
+ encode(notify_handle->pending_watcher_ids, *notify_handle->pbl);
+ }
+
+ notify_handle->rados_client->get_aio_finisher()->queue(
+ notify_handle->on_notify, 0);
+ watcher->notify_handles.erase(notify_id);
+ maybe_remove_watcher(watcher);
+}
+
+void TestWatchNotify::blocklist(uint32_t nonce) {
+ std::lock_guard locker{m_lock};
+
+ for (auto file_it = m_file_watchers.begin();
+ file_it != m_file_watchers.end(); ) {
+ auto &watcher = file_it->second;
+ for (auto w_it = watcher->watch_handles.begin();
+ w_it != watcher->watch_handles.end();) {
+ auto& watch_handle = w_it->second;
+ if (watch_handle.nonce == nonce) {
+ auto handle = watch_handle.handle;
+ auto watch_ctx2 = watch_handle.watch_ctx2;
+ if (watch_ctx2 != nullptr) {
+ auto ctx = new LambdaContext([handle, watch_ctx2](int) {
+ watch_ctx2->handle_error(handle, -ENOTCONN);
+ });
+ watch_handle.rados_client->get_aio_finisher()->queue(ctx);
+ }
+ w_it = watcher->watch_handles.erase(w_it);
+ } else {
+ ++w_it;
+ }
+ }
+
+ ++file_it;
+ maybe_remove_watcher(watcher);
+ }
+}
+
+void TestWatchNotify::handle_object_removed(int64_t pool_id,
+ const std::string& nspace,
+ const std::string& oid) {
+ std::lock_guard locker{m_lock};
+ auto it = m_file_watchers.find({pool_id, nspace, oid});
+ if (it == m_file_watchers.end()) {
+ return;
+ }
+
+ auto watcher = it->second;
+
+ // cancel all in-flight notifications
+ for (auto& notify_handle_pair : watcher->notify_handles) {
+ auto notify_handle = notify_handle_pair.second;
+ notify_handle->rados_client->get_aio_finisher()->queue(
+ notify_handle->on_notify, -ENOENT);
+ }
+
+ // alert all watchers of the loss of connection
+ for (auto& watch_handle_pair : watcher->watch_handles) {
+ auto& watch_handle = watch_handle_pair.second;
+ auto handle = watch_handle.handle;
+ auto watch_ctx2 = watch_handle.watch_ctx2;
+ if (watch_ctx2 != nullptr) {
+ auto ctx = new LambdaContext([handle, watch_ctx2](int) {
+ watch_ctx2->handle_error(handle, -ENOTCONN);
+ });
+ watch_handle.rados_client->get_aio_finisher()->queue(ctx);
+ }
+ }
+ m_file_watchers.erase(it);
+}
+
+} // namespace librados
diff --git a/src/test/librados_test_stub/TestWatchNotify.h b/src/test/librados_test_stub/TestWatchNotify.h
new file mode 100644
index 000000000..bb973ea6b
--- /dev/null
+++ b/src/test/librados_test_stub/TestWatchNotify.h
@@ -0,0 +1,148 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_WATCH_NOTIFY_H
+#define CEPH_TEST_WATCH_NOTIFY_H
+
+#include "include/rados/librados.hpp"
+#include "common/AsyncOpTracker.h"
+#include "common/ceph_mutex.h"
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <list>
+#include <map>
+
+class Finisher;
+
+namespace librados {
+
+class TestCluster;
+class TestRadosClient;
+
+class TestWatchNotify : boost::noncopyable {
+public:
+ typedef std::pair<uint64_t, uint64_t> WatcherID;
+ typedef std::set<WatcherID> WatcherIDs;
+ typedef std::map<std::pair<uint64_t, uint64_t>, bufferlist> NotifyResponses;
+
+ struct NotifyHandle {
+ TestRadosClient *rados_client = nullptr;
+ WatcherIDs pending_watcher_ids;
+ NotifyResponses notify_responses;
+ bufferlist *pbl = nullptr;
+ Context *on_notify = nullptr;
+ };
+ typedef boost::shared_ptr<NotifyHandle> SharedNotifyHandle;
+ typedef std::map<uint64_t, SharedNotifyHandle> NotifyHandles;
+
+ struct WatchHandle {
+ TestRadosClient *rados_client = nullptr;
+ std::string addr;
+ uint32_t nonce;
+ uint64_t gid;
+ uint64_t handle;
+ librados::WatchCtx* watch_ctx;
+ librados::WatchCtx2* watch_ctx2;
+ };
+
+ typedef std::map<uint64_t, WatchHandle> WatchHandles;
+
+ struct ObjectHandler;
+ typedef boost::shared_ptr<ObjectHandler> SharedObjectHandler;
+
+ struct Watcher {
+ Watcher(int64_t pool_id, const std::string& nspace, const std::string& oid)
+ : pool_id(pool_id), nspace(nspace), oid(oid) {
+ }
+
+ int64_t pool_id;
+ std::string nspace;
+ std::string oid;
+
+ SharedObjectHandler object_handler;
+ WatchHandles watch_handles;
+ NotifyHandles notify_handles;
+ };
+ typedef boost::shared_ptr<Watcher> SharedWatcher;
+
+ TestWatchNotify(TestCluster* test_cluster);
+
+ int list_watchers(int64_t pool_id, const std::string& nspace,
+ const std::string& o, std::list<obj_watch_t> *out_watchers);
+
+ void aio_flush(TestRadosClient *rados_client, Context *on_finish);
+ void aio_watch(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string& o, uint64_t gid,
+ uint64_t *handle, librados::WatchCtx *watch_ctx,
+ librados::WatchCtx2 *watch_ctx2, Context *on_finish);
+ void aio_unwatch(TestRadosClient *rados_client, uint64_t handle,
+ Context *on_finish);
+ void aio_notify(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string& oid,
+ const bufferlist& bl, uint64_t timeout_ms, bufferlist *pbl,
+ Context *on_notify);
+
+ void flush(TestRadosClient *rados_client);
+ int notify(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string& o, bufferlist& bl,
+ uint64_t timeout_ms, bufferlist *pbl);
+ void notify_ack(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string& o,
+ uint64_t notify_id, uint64_t handle, uint64_t gid,
+ bufferlist& bl);
+
+ int watch(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string& o, uint64_t gid,
+ uint64_t *handle, librados::WatchCtx *ctx,
+ librados::WatchCtx2 *ctx2);
+ int unwatch(TestRadosClient *rados_client, uint64_t handle);
+
+ void blocklist(uint32_t nonce);
+
+private:
+ typedef std::tuple<int64_t, std::string, std::string> PoolFile;
+ typedef std::map<PoolFile, SharedWatcher> FileWatchers;
+
+ TestCluster *m_test_cluster;
+
+ uint64_t m_handle = 0;
+ uint64_t m_notify_id = 0;
+
+ ceph::mutex m_lock =
+ ceph::make_mutex("librados::TestWatchNotify::m_lock");
+ AsyncOpTracker m_async_op_tracker;
+
+ FileWatchers m_file_watchers;
+
+ SharedWatcher get_watcher(int64_t pool_id, const std::string& nspace,
+ const std::string& oid);
+ void maybe_remove_watcher(SharedWatcher shared_watcher);
+
+ void execute_watch(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string& o,
+ uint64_t gid, uint64_t *handle,
+ librados::WatchCtx *watch_ctx,
+ librados::WatchCtx2 *watch_ctx2,
+ Context *on_finish);
+ void execute_unwatch(TestRadosClient *rados_client, uint64_t handle,
+ Context *on_finish);
+
+ void execute_notify(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string &oid,
+ const bufferlist &bl, bufferlist *pbl,
+ Context *on_notify);
+ void ack_notify(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string &oid,
+ uint64_t notify_id, const WatcherID &watcher_id,
+ const bufferlist &bl);
+ void finish_notify(TestRadosClient *rados_client, int64_t pool_id,
+ const std::string& nspace, const std::string &oid,
+ uint64_t notify_id);
+
+ void handle_object_removed(int64_t pool_id, const std::string& nspace,
+ const std::string& oid);
+};
+
+} // namespace librados
+
+#endif // CEPH_TEST_WATCH_NOTIFY_H
diff --git a/src/test/libradosstriper/CMakeLists.txt b/src/test/libradosstriper/CMakeLists.txt
new file mode 100644
index 000000000..8e53a300a
--- /dev/null
+++ b/src/test/libradosstriper/CMakeLists.txt
@@ -0,0 +1,34 @@
+#
+# Note: only compiled if WITH_LIBRADOSSTRIPER is defined.
+#
+add_library(rados_striper_test STATIC TestCase.cc)
+target_link_libraries(rados_striper_test
+ radostest
+ radostest-cxx
+ GTest::GTest)
+
+add_executable(ceph_test_rados_striper_api_striping
+ striping.cc
+ )
+target_link_libraries(ceph_test_rados_striper_api_striping
+ ${UNITTEST_LIBS} rados_striper_test
+ radosstriper
+ librados)
+install(TARGETS ceph_test_rados_striper_api_striping
+ DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+add_executable(ceph_test_rados_striper_api_io
+ io.cc)
+target_link_libraries(ceph_test_rados_striper_api_io
+ ${UNITTEST_LIBS} rados_striper_test
+ radosstriper
+ librados)
+install(TARGETS ceph_test_rados_striper_api_io
+ DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+add_executable(ceph_test_rados_striper_api_aio
+ aio.cc)
+target_link_libraries(ceph_test_rados_striper_api_aio librados radosstriper
+ ${UNITTEST_LIBS} rados_striper_test)
+install(TARGETS ceph_test_rados_striper_api_aio
+ DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/test/libradosstriper/TestCase.cc b/src/test/libradosstriper/TestCase.cc
new file mode 100644
index 000000000..98e81f49c
--- /dev/null
+++ b/src/test/libradosstriper/TestCase.cc
@@ -0,0 +1,80 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <errno.h>
+#include "test/librados/test.h"
+#include "test/librados/test_cxx.h"
+#include "test/libradosstriper/TestCase.h"
+
+using namespace libradosstriper;
+
+std::string StriperTest::pool_name;
+rados_t StriperTest::s_cluster = NULL;
+
+void StriperTest::SetUpTestCase()
+{
+ pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool(pool_name, &s_cluster));
+}
+
+void StriperTest::TearDownTestCase()
+{
+ ASSERT_EQ(0, destroy_one_pool(pool_name, &s_cluster));
+}
+
+void StriperTest::SetUp()
+{
+ cluster = StriperTest::s_cluster;
+ ASSERT_EQ(0, rados_ioctx_create(cluster, pool_name.c_str(), &ioctx));
+ ASSERT_EQ(0, rados_striper_create(ioctx, &striper));
+}
+
+void StriperTest::TearDown()
+{
+ rados_striper_destroy(striper);
+ rados_ioctx_destroy(ioctx);
+}
+
+std::string StriperTestPP::pool_name;
+librados::Rados StriperTestPP::s_cluster;
+
+void StriperTestPP::SetUpTestCase()
+{
+ pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool_pp(pool_name, s_cluster));
+}
+
+void StriperTestPP::TearDownTestCase()
+{
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, s_cluster));
+}
+
+void StriperTestPP::SetUp()
+{
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx));
+ ASSERT_EQ(0, RadosStriper::striper_create(ioctx, &striper));
+}
+
+// this is pure copy and paste from previous class
+// but for the inheritance from TestWithParam
+// with gtest >= 1.6, we couldd avoid this by using
+// inheritance from WithParamInterface
+std::string StriperTestParam::pool_name;
+librados::Rados StriperTestParam::s_cluster;
+
+void StriperTestParam::SetUpTestCase()
+{
+ pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool_pp(pool_name, s_cluster));
+}
+
+void StriperTestParam::TearDownTestCase()
+{
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, s_cluster));
+}
+
+void StriperTestParam::SetUp()
+{
+ ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx));
+ ASSERT_EQ(0, RadosStriper::striper_create(ioctx, &striper));
+}
diff --git a/src/test/libradosstriper/TestCase.h b/src/test/libradosstriper/TestCase.h
new file mode 100644
index 000000000..c316b3bfb
--- /dev/null
+++ b/src/test/libradosstriper/TestCase.h
@@ -0,0 +1,82 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_RADOS_TESTCASE_H
+#define CEPH_TEST_RADOS_TESTCASE_H
+
+#include "include/rados/librados.h"
+#include "include/rados/librados.hpp"
+#include "include/radosstriper/libradosstriper.h"
+#include "include/radosstriper/libradosstriper.hpp"
+#include "gtest/gtest.h"
+
+#include <string>
+
+/**
+ * These test cases create a temporary pool that lives as long as the
+ * test case. Each test within a test case gets a new ioctx and striper
+ * set to a unique namespace within the pool.
+ *
+ * Since pool creation and deletion is slow, this allows many tests to
+ * run faster.
+ */
+class StriperTest : public ::testing::Test {
+public:
+ StriperTest() {}
+ ~StriperTest() override {}
+protected:
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+ static rados_t s_cluster;
+ static std::string pool_name;
+
+ void SetUp() override;
+ void TearDown() override;
+ rados_t cluster = NULL;
+ rados_ioctx_t ioctx = NULL;
+ rados_striper_t striper = NULL;
+};
+
+class StriperTestPP : public ::testing::Test {
+public:
+ StriperTestPP() : cluster(s_cluster) {}
+ ~StriperTestPP() override {}
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+protected:
+ static librados::Rados s_cluster;
+ static std::string pool_name;
+
+ void SetUp() override;
+ librados::Rados &cluster;
+ librados::IoCtx ioctx;
+ libradosstriper::RadosStriper striper;
+};
+
+struct TestData {
+ uint32_t stripe_unit;
+ uint32_t stripe_count;
+ uint32_t object_size;
+ size_t size;
+};
+// this is pure copy and paste from previous class
+// but for the inheritance from TestWithParam
+// with gtest >= 1.6, we couldd avoid this by using
+// inheritance from WithParamInterface
+class StriperTestParam : public ::testing::TestWithParam<TestData> {
+public:
+ StriperTestParam() : cluster(s_cluster) {}
+ ~StriperTestParam() override {}
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+protected:
+ static librados::Rados s_cluster;
+ static std::string pool_name;
+
+ void SetUp() override;
+ librados::Rados &cluster;
+ librados::IoCtx ioctx;
+ libradosstriper::RadosStriper striper;
+};
+
+#endif
diff --git a/src/test/libradosstriper/aio.cc b/src/test/libradosstriper/aio.cc
new file mode 100644
index 000000000..71e6abd7a
--- /dev/null
+++ b/src/test/libradosstriper/aio.cc
@@ -0,0 +1,581 @@
+#include "include/rados/librados.h"
+#include "include/rados/librados.hpp"
+#include "include/radosstriper/libradosstriper.h"
+#include "include/radosstriper/libradosstriper.hpp"
+#include "test/librados/test.h"
+#include "test/libradosstriper/TestCase.h"
+
+#include <boost/scoped_ptr.hpp>
+#include <fcntl.h>
+#include <semaphore.h>
+#include <errno.h>
+
+using namespace librados;
+using namespace libradosstriper;
+using std::pair;
+
+class AioTestData
+{
+public:
+ AioTestData() : m_complete(false) {
+ sem_init(&m_sem, 0, 0);
+ }
+
+ ~AioTestData() {
+ sem_destroy(&m_sem);
+ }
+
+ void notify() {
+ sem_post(&m_sem);
+ }
+
+ void wait() {
+ sem_wait(&m_sem);
+ }
+
+ bool m_complete;
+
+private:
+ sem_t m_sem;
+};
+
+void set_completion_complete(rados_completion_t cb, void *arg)
+{
+ AioTestData *test = static_cast<AioTestData*>(arg);
+ test->m_complete = true;
+ test->notify();
+}
+
+TEST_F(StriperTest, SimpleWrite) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_aio_write(striper, "StriperTest", my_completion, buf, sizeof(buf), 0));
+ TestAlarm alarm;
+ test_data.wait();
+ rados_aio_release(my_completion);
+}
+
+TEST_F(StriperTestPP, SimpleWritePP) {
+ AioTestData test_data;
+ AioCompletion *my_completion = librados::Rados::aio_create_completion
+ ((void*)&test_data, set_completion_complete);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.aio_write("SimpleWritePP", my_completion, bl1, sizeof(buf), 0));
+ TestAlarm alarm;
+ test_data.wait();
+ my_completion->release();
+}
+
+TEST_F(StriperTest, WaitForSafe) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_aio_write(striper, "WaitForSafe", my_completion, buf, sizeof(buf), 0));
+ TestAlarm alarm;
+ rados_aio_wait_for_complete(my_completion);
+ test_data.wait();
+ rados_aio_release(my_completion);
+}
+
+TEST_F(StriperTestPP, WaitForSafePP) {
+ AioTestData test_data;
+ AioCompletion *my_completion =
+ librados::Rados::aio_create_completion(&test_data,
+ set_completion_complete);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.aio_write("WaitForSafePP", my_completion, bl1, sizeof(buf), 0));
+ TestAlarm alarm;
+ my_completion->wait_for_complete();
+ test_data.wait();
+ my_completion->release();
+}
+
+TEST_F(StriperTest, RoundTrip) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_aio_write(striper, "RoundTrip", my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ test_data.wait();
+ }
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion2));
+ ASSERT_EQ(0, rados_striper_aio_read(striper, "RoundTrip", my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ rados_aio_wait_for_complete(my_completion2);
+ }
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ test_data.wait();
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST_F(StriperTest, RoundTrip2) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_aio_write(striper, "RoundTrip2", my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ test_data.wait();
+ }
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion2));
+ ASSERT_EQ(0, rados_striper_aio_read(striper, "RoundTrip2", my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ rados_aio_wait_for_complete(my_completion2);
+ }
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ test_data.wait();
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST_F(StriperTestPP, RoundTripPP) {
+ AioTestData test_data;
+ AioCompletion *my_completion =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.aio_write("RoundTripPP", my_completion, bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ test_data.wait();
+ }
+ bufferlist bl2;
+ AioCompletion *my_completion2 =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ ASSERT_EQ(0, striper.aio_read("RoundTripPP", my_completion2, &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ my_completion2->wait_for_complete();
+ }
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+ test_data.wait();
+ my_completion->release();
+ my_completion2->release();
+}
+
+TEST_F(StriperTestPP, RoundTripPP2) {
+ AioTestData test_data;
+ AioCompletion *my_completion =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.aio_write("RoundTripPP2", my_completion, bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ test_data.wait();
+ }
+ bufferlist bl2;
+ AioCompletion *my_completion2 =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ ASSERT_EQ(0, striper.aio_read("RoundTripPP2", my_completion2, &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ my_completion2->wait_for_complete();
+ }
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+ test_data.wait();
+ my_completion->release();
+ my_completion2->release();
+}
+
+TEST_F(StriperTest, IsComplete) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_aio_write(striper, "IsComplete", my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ test_data.wait();
+ }
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion2));
+ ASSERT_EQ(0, rados_striper_aio_read(striper, "IsComplete", my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ // Busy-wait until the AIO completes.
+ // Normally we wouldn't do this, but we want to test rados_aio_is_complete.
+ while (true) {
+ int is_complete = rados_aio_is_complete(my_completion2);
+ if (is_complete)
+ break;
+ }
+ }
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ test_data.wait();
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST_F(StriperTestPP, IsCompletePP) {
+ AioTestData test_data;
+ AioCompletion *my_completion =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.aio_write("IsCompletePP", my_completion, bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ test_data.wait();
+ }
+ bufferlist bl2;
+ AioCompletion *my_completion2 =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ ASSERT_EQ(0, striper.aio_read("IsCompletePP", my_completion2, &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ // Busy-wait until the AIO completes.
+ // Normally we wouldn't do this, but we want to test rados_aio_is_complete.
+ while (true) {
+ int is_complete = my_completion2->is_complete();
+ if (is_complete)
+ break;
+ }
+ }
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+ test_data.wait();
+ my_completion->release();
+ my_completion2->release();
+}
+
+TEST_F(StriperTest, IsSafe) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_aio_write(striper, "IsSafe", my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ // Busy-wait until the AIO completes.
+ // Normally we wouldn't do this, but we want to test rados_aio_is_safe.
+ while (true) {
+ int is_safe = rados_aio_is_safe(my_completion);
+ if (is_safe)
+ break;
+ }
+ }
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion2));
+ ASSERT_EQ(0, rados_striper_aio_read(striper, "IsSafe", my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ rados_aio_wait_for_complete(my_completion2);
+ }
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ test_data.wait();
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST_F(StriperTest, RoundTripAppend) {
+ AioTestData test_data;
+ rados_completion_t my_completion, my_completion2, my_completion3;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_aio_append(striper, "RoundTripAppend", my_completion, buf, sizeof(buf)));
+ {
+ TestAlarm alarm;
+ rados_aio_wait_for_complete(my_completion);
+ }
+ char buf2[128];
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion2));
+ ASSERT_EQ(0, rados_striper_aio_append(striper, "RoundTripAppend", my_completion2, buf2, sizeof(buf)));
+ {
+ TestAlarm alarm;
+ rados_aio_wait_for_complete(my_completion2);
+ }
+ char buf3[sizeof(buf) + sizeof(buf2)];
+ memset(buf3, 0, sizeof(buf3));
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion3));
+ ASSERT_EQ(0, rados_striper_aio_read(striper, "RoundTripAppend", my_completion3, buf3, sizeof(buf3), 0));
+ {
+ TestAlarm alarm;
+ rados_aio_wait_for_complete(my_completion3);
+ }
+ ASSERT_EQ((int)(sizeof(buf) + sizeof(buf2)), rados_aio_get_return_value(my_completion3));
+ ASSERT_EQ(0, memcmp(buf3, buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(buf3 + sizeof(buf), buf2, sizeof(buf2)));
+ test_data.wait();
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+}
+
+TEST_F(StriperTestPP, RoundTripAppendPP) {
+ AioTestData test_data;
+ AioCompletion *my_completion =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.aio_append("RoundTripAppendPP", my_completion, bl1, sizeof(buf)));
+ {
+ TestAlarm alarm;
+ my_completion->wait_for_complete();
+ }
+ char buf2[128];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ AioCompletion *my_completion2 =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ ASSERT_EQ(0, striper.aio_append("RoundTripAppendPP", my_completion2, bl2, sizeof(buf2)));
+ {
+ TestAlarm alarm;
+ my_completion2->wait_for_complete();
+ }
+ bufferlist bl3;
+ AioCompletion *my_completion3 =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ ASSERT_EQ(0, striper.aio_read("RoundTripAppendPP", my_completion3, &bl3, 2 * sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ my_completion3->wait_for_complete();
+ }
+ ASSERT_EQ(sizeof(buf) + sizeof(buf2), (unsigned)my_completion3->get_return_value());
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(bl3.c_str() + sizeof(buf), buf2, sizeof(buf2)));
+ test_data.wait();
+ my_completion->release();
+ my_completion2->release();
+ my_completion3->release();
+}
+
+TEST_F(StriperTest, Flush) {
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion));
+ char buf[128];
+ memset(buf, 0xee, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_aio_write(striper, "Flush", my_completion, buf, sizeof(buf), 0));
+ rados_striper_aio_flush(striper);
+ char buf2[128];
+ memset(buf2, 0, sizeof(buf2));
+ rados_completion_t my_completion2;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion2));
+ ASSERT_EQ(0, rados_striper_aio_read(striper, "Flush", my_completion2, buf2, sizeof(buf2), 0));
+ {
+ TestAlarm alarm;
+ rados_aio_wait_for_complete(my_completion2);
+ }
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ test_data.wait();
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+}
+
+TEST_F(StriperTestPP, FlushPP) {
+ AioTestData test_data;
+ AioCompletion *my_completion =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ char buf[128];
+ memset(buf, 0xee, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.aio_write("FlushPP", my_completion, bl1, sizeof(buf), 0));
+ striper.aio_flush();
+ bufferlist bl2;
+ AioCompletion *my_completion2 =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ ASSERT_EQ(0, striper.aio_read("FlushPP", my_completion2, &bl2, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ my_completion2->wait_for_complete();
+ }
+ ASSERT_EQ(0, memcmp(buf, bl2.c_str(), sizeof(buf)));
+ test_data.wait();
+ my_completion->release();
+ my_completion2->release();
+}
+
+TEST_F(StriperTest, RoundTripWriteFull) {
+ AioTestData test_data;
+ rados_completion_t my_completion, my_completion2, my_completion3;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion));
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_aio_write(striper, "RoundTripWriteFull", my_completion, buf, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ rados_aio_wait_for_complete(my_completion);
+ }
+ char buf2[64];
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion2));
+ ASSERT_EQ(0, rados_striper_aio_write_full(striper, "RoundTripWriteFull", my_completion2, buf2, sizeof(buf2)));
+ {
+ TestAlarm alarm;
+ rados_aio_wait_for_complete(my_completion2);
+ }
+ char buf3[sizeof(buf) + sizeof(buf2)];
+ memset(buf3, 0, sizeof(buf3));
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion3));
+ ASSERT_EQ(0, rados_striper_aio_read(striper, "RoundTripWriteFull", my_completion3, buf3, sizeof(buf3), 0));
+ {
+ TestAlarm alarm;
+ rados_aio_wait_for_complete(my_completion3);
+ }
+ ASSERT_EQ(sizeof(buf2), (unsigned)rados_aio_get_return_value(my_completion3));
+ ASSERT_EQ(0, memcmp(buf3, buf2, sizeof(buf2)));
+ test_data.wait();
+ rados_aio_release(my_completion);
+ rados_aio_release(my_completion2);
+ rados_aio_release(my_completion3);
+}
+
+TEST_F(StriperTestPP, RoundTripWriteFullPP) {
+ AioTestData test_data;
+ AioCompletion *my_completion =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.aio_write("RoundTripWriteFullPP", my_completion, bl1, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ my_completion->wait_for_complete();
+ }
+ char buf2[64];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ AioCompletion *my_completion2 =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ ASSERT_EQ(0, striper.aio_write_full("RoundTripWriteFullPP", my_completion2, bl2));
+ {
+ TestAlarm alarm;
+ my_completion2->wait_for_complete();
+ }
+ bufferlist bl3;
+ AioCompletion *my_completion3 =
+ librados::Rados::aio_create_completion(&test_data, set_completion_complete);
+ ASSERT_EQ(0, striper.aio_read("RoundTripWriteFullPP", my_completion3, &bl3, sizeof(buf), 0));
+ {
+ TestAlarm alarm;
+ my_completion3->wait_for_complete();
+ }
+ ASSERT_EQ(sizeof(buf2), (unsigned)my_completion3->get_return_value());
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf2, sizeof(buf2)));
+ test_data.wait();
+ my_completion->release();
+ my_completion2->release();
+ my_completion3->release();
+}
+
+TEST_F(StriperTest, RemoveTest) {
+ char buf[128];
+ char buf2[sizeof(buf)];
+ // create oabject
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "RemoveTest", buf, sizeof(buf), 0));
+ // async remove it
+ AioTestData test_data;
+ rados_completion_t my_completion;
+ ASSERT_EQ(0, rados_aio_create_completion2(&test_data,
+ set_completion_complete,
+ &my_completion));
+ ASSERT_EQ(0, rados_striper_aio_remove(striper, "RemoveTest", my_completion));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, rados_aio_wait_for_complete(my_completion));
+ }
+ ASSERT_EQ(0, rados_aio_get_return_value(my_completion));
+ rados_aio_release(my_completion);
+ // check we get ENOENT on reading
+ ASSERT_EQ(-ENOENT, rados_striper_read(striper, "RemoveTest", buf2, sizeof(buf2), 0));
+}
+
+TEST_F(StriperTestPP, RemoveTestPP) {
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("RemoveTestPP", bl, sizeof(buf), 0));
+ AioCompletion *my_completion = cluster.aio_create_completion(nullptr, nullptr);
+ ASSERT_EQ(0, striper.aio_remove("RemoveTestPP", my_completion));
+ {
+ TestAlarm alarm;
+ ASSERT_EQ(0, my_completion->wait_for_complete());
+ }
+ ASSERT_EQ(0, my_completion->get_return_value());
+ bufferlist bl2;
+ ASSERT_EQ(-ENOENT, striper.read("RemoveTestPP", &bl2, sizeof(buf), 0));
+ my_completion->release();
+}
diff --git a/src/test/libradosstriper/io.cc b/src/test/libradosstriper/io.cc
new file mode 100644
index 000000000..88f69400e
--- /dev/null
+++ b/src/test/libradosstriper/io.cc
@@ -0,0 +1,430 @@
+#include "include/rados/librados.h"
+#include "include/rados/librados.hpp"
+#include "include/radosstriper/libradosstriper.h"
+#include "include/radosstriper/libradosstriper.hpp"
+#include "test/librados/test.h"
+#include "test/libradosstriper/TestCase.h"
+
+#include <fcntl.h>
+#include <errno.h>
+#include "gtest/gtest.h"
+
+using namespace librados;
+using namespace libradosstriper;
+using std::string;
+
+TEST_F(StriperTest, SimpleWrite) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "SimpleWrite", buf, sizeof(buf), 0));
+}
+
+TEST_F(StriperTestPP, SimpleWritePP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("SimpleWritePP", bl, sizeof(buf), 0));
+}
+
+TEST_F(StriperTest, SimpleWriteFull) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write_full(striper, "SimpleWrite", buf, sizeof(buf)));
+}
+
+TEST_F(StriperTestPP, SimpleWriteFullPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write_full("SimpleWritePP", bl));
+}
+
+TEST_F(StriperTest, Stat) {
+ uint64_t size = 0;
+ time_t mtime = 0;
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "Stat", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_striper_stat(striper, "Stat", &size, &mtime));
+ ASSERT_EQ(size, sizeof(buf));
+ ASSERT_EQ(-ENOENT, rados_striper_stat(striper, "nonexistent", &size, &mtime));
+}
+
+TEST_F(StriperTest, Stat2) {
+ uint64_t size = 0;
+ struct timespec mtime = {};
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "Stat2", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_striper_stat2(striper, "Stat2", &size, &mtime));
+ ASSERT_EQ(size, sizeof(buf));
+ ASSERT_EQ(-ENOENT, rados_striper_stat2(striper, "nonexistent", &size, &mtime));
+}
+
+TEST_F(StriperTestPP, StatPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("Statpp", bl, sizeof(buf), 0));
+ uint64_t size = 0;
+ time_t mtime = 0;
+ ASSERT_EQ(0, striper.stat("Statpp", &size, &mtime));
+ ASSERT_EQ(size, sizeof(buf));
+ ASSERT_EQ(-ENOENT, striper.stat("nonexistent", &size, &mtime));
+}
+
+TEST_F(StriperTestPP, Stat2PP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("Stat2pp", bl, sizeof(buf), 0));
+ uint64_t size = 0;
+ struct timespec mtime = {};
+ ASSERT_EQ(0, striper.stat2("Stat2pp", &size, &mtime));
+ ASSERT_EQ(size, sizeof(buf));
+ ASSERT_EQ(-ENOENT, striper.stat2("nonexistent", &size, &mtime));
+}
+
+TEST_F(StriperTest, RoundTrip) {
+ char buf[128];
+ char buf2[sizeof(buf)];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "RoundTrip", buf, sizeof(buf), 0));
+ memset(buf2, 0, sizeof(buf2));
+ ASSERT_EQ((int)sizeof(buf2), rados_striper_read(striper, "RoundTrip", buf2, sizeof(buf2), 0));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+}
+
+TEST_F(StriperTestPP, RoundTripPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("RoundTripPP", bl, sizeof(buf), 0));
+ bufferlist cl;
+ ASSERT_EQ((int)sizeof(buf), striper.read("RoundTripPP", &cl, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(buf, cl.c_str(), sizeof(buf)));
+}
+
+TEST_F(StriperTest, OverlappingWriteRoundTrip) {
+ char buf[128];
+ char buf2[64];
+ char buf3[sizeof(buf)];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "OverlappingWriteRoundTrip", buf, sizeof(buf), 0));
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_striper_write(striper, "OverlappingWriteRoundTrip", buf2, sizeof(buf2), 0));
+ memset(buf3, 0, sizeof(buf3));
+ ASSERT_EQ((int)sizeof(buf3), rados_striper_read(striper, "OverlappingWriteRoundTrip", buf3, sizeof(buf3), 0));
+ ASSERT_EQ(0, memcmp(buf3, buf2, sizeof(buf2)));
+ ASSERT_EQ(0, memcmp(buf3 + sizeof(buf2), buf, sizeof(buf) - sizeof(buf2)));
+}
+
+TEST_F(StriperTestPP, OverlappingWriteRoundTripPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("OverlappingWriteRoundTripPP", bl1, sizeof(buf), 0));
+ char buf2[64];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ASSERT_EQ(0, striper.write("OverlappingWriteRoundTripPP", bl2, sizeof(buf2), 0));
+ bufferlist bl3;
+ ASSERT_EQ((int)sizeof(buf), striper.read("OverlappingWriteRoundTripPP", &bl3, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf2, sizeof(buf2)));
+ ASSERT_EQ(0, memcmp(bl3.c_str() + sizeof(buf2), buf, sizeof(buf) - sizeof(buf2)));
+}
+
+TEST_F(StriperTest, SparseWriteRoundTrip) {
+ char buf[128];
+ char buf2[2*sizeof(buf)];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "SparseWriteRoundTrip", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_striper_write(striper, "SparseWriteRoundTrip", buf, sizeof(buf), 1000000000));
+ memset(buf2, 0xaa, sizeof(buf2));
+ ASSERT_EQ((int)sizeof(buf2), rados_striper_read(striper, "SparseWriteRoundTrip", buf2, sizeof(buf2), 0));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ memset(buf, 0, sizeof(buf));
+ ASSERT_EQ(0, memcmp(buf, buf2+sizeof(buf), sizeof(buf)));
+ memset(buf2, 0xaa, sizeof(buf2));
+ ASSERT_EQ((int)sizeof(buf), rados_striper_read(striper, "SparseWriteRoundTrip", buf2, sizeof(buf), 500000000));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+}
+
+TEST_F(StriperTestPP, SparseWriteRoundTripPP) {
+ char buf[128];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("SparseWriteRoundTripPP", bl1, sizeof(buf), 0));
+ ASSERT_EQ(0, striper.write("SparseWriteRoundTripPP", bl1, sizeof(buf), 1000000000));
+ bufferlist bl2;
+ ASSERT_EQ((int)(2*sizeof(buf)), striper.read("SparseWriteRoundTripPP", &bl2, 2*sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(bl2.c_str(), buf, sizeof(buf)));
+ memset(buf, 0, sizeof(buf));
+ ASSERT_EQ(0, memcmp(bl2.c_str()+sizeof(buf), buf, sizeof(buf)));
+ ASSERT_EQ((int)sizeof(buf), striper.read("SparseWriteRoundTripPP", &bl2, sizeof(buf), 500000000));
+ ASSERT_EQ(0, memcmp(bl2.c_str(), buf, sizeof(buf)));
+}
+
+TEST_F(StriperTest, WriteFullRoundTrip) {
+ char buf[128];
+ char buf2[64];
+ char buf3[128];
+ memset(buf, 0xcc, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "WriteFullRoundTrip", buf, sizeof(buf), 0));
+ memset(buf2, 0xdd, sizeof(buf2));
+ ASSERT_EQ(0, rados_striper_write_full(striper, "WriteFullRoundTrip", buf2, sizeof(buf2)));
+ memset(buf3, 0x00, sizeof(buf3));
+ ASSERT_EQ((int)sizeof(buf2), rados_striper_read(striper, "WriteFullRoundTrip", buf3, sizeof(buf3), 0));
+ ASSERT_EQ(0, memcmp(buf2, buf3, sizeof(buf2)));
+}
+
+TEST_F(StriperTestPP, WriteFullRoundTripPP) {
+ char buf[128];
+ char buf2[64];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("WriteFullRoundTripPP", bl1, sizeof(buf), 0));
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ASSERT_EQ(0, striper.write_full("WriteFullRoundTripPP", bl2));
+ bufferlist bl3;
+ ASSERT_EQ((int)sizeof(buf2), striper.read("WriteFullRoundTripPP", &bl3, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(bl3.c_str(), buf2, sizeof(buf2)));
+}
+
+TEST_F(StriperTest, AppendRoundTrip) {
+ char buf[64];
+ char buf2[64];
+ char buf3[sizeof(buf) + sizeof(buf2)];
+ memset(buf, 0xde, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_append(striper, "AppendRoundTrip", buf, sizeof(buf)));
+ memset(buf2, 0xad, sizeof(buf2));
+ ASSERT_EQ(0, rados_striper_append(striper, "AppendRoundTrip", buf2, sizeof(buf2)));
+ memset(buf3, 0, sizeof(buf3));
+ ASSERT_EQ((int)sizeof(buf3), rados_striper_read(striper, "AppendRoundTrip", buf3, sizeof(buf3), 0));
+ ASSERT_EQ(0, memcmp(buf3, buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(buf3 + sizeof(buf), buf2, sizeof(buf2)));
+}
+
+TEST_F(StriperTestPP, AppendRoundTripPP) {
+ char buf[64];
+ char buf2[64];
+ memset(buf, 0xde, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.append("AppendRoundTripPP", bl1, sizeof(buf)));
+ memset(buf2, 0xad, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ ASSERT_EQ(0, striper.append("AppendRoundTripPP", bl2, sizeof(buf2)));
+ bufferlist bl3;
+ ASSERT_EQ((int)(sizeof(buf) + sizeof(buf2)),
+ striper.read("AppendRoundTripPP", &bl3, (sizeof(buf) + sizeof(buf2)), 0));
+ const char *bl3_str = bl3.c_str();
+ ASSERT_EQ(0, memcmp(bl3_str, buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(bl3_str + sizeof(buf), buf2, sizeof(buf2)));
+}
+
+TEST_F(StriperTest, TruncTest) {
+ char buf[128];
+ char buf2[sizeof(buf)];
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_append(striper, "TruncTest", buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_striper_trunc(striper, "TruncTest", sizeof(buf) / 2));
+ memset(buf2, 0, sizeof(buf2));
+ ASSERT_EQ((int)(sizeof(buf)/2), rados_striper_read(striper, "TruncTest", buf2, sizeof(buf2), 0));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)/2));
+}
+
+TEST_F(StriperTestPP, TruncTestPP) {
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.append("TruncTestPP", bl, sizeof(buf)));
+ ASSERT_EQ(0, striper.trunc("TruncTestPP", sizeof(buf) / 2));
+ bufferlist bl2;
+ ASSERT_EQ((int)(sizeof(buf)/2), striper.read("TruncTestPP", &bl2, sizeof(buf), 0));
+ ASSERT_EQ(0, memcmp(bl2.c_str(), buf, sizeof(buf)/2));
+}
+
+TEST_F(StriperTest, TruncTestGrow) {
+ char buf[128];
+ char buf2[sizeof(buf)*2];
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_append(striper, "TruncTestGrow", buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_striper_trunc(striper, "TruncTestGrow", sizeof(buf2)));
+ memset(buf2, 0xbb, sizeof(buf2));
+ ASSERT_EQ((int)sizeof(buf2), rados_striper_read(striper, "TruncTestGrow", buf2, sizeof(buf2), 0));
+ ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
+ memset(buf, 0x00, sizeof(buf));
+ ASSERT_EQ(0, memcmp(buf, buf2+sizeof(buf), sizeof(buf)));
+}
+
+TEST_F(StriperTestPP, TruncTestGrowPP) {
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.append("TruncTestGrowPP", bl, sizeof(buf)));
+ ASSERT_EQ(0, striper.trunc("TruncTestGrowPP", sizeof(buf) * 2));
+ bufferlist bl2;
+ ASSERT_EQ(sizeof(buf)*2, (unsigned)striper.read("TruncTestGrowPP", &bl2, sizeof(buf)*2, 0));
+ ASSERT_EQ(0, memcmp(bl2.c_str(), buf, sizeof(buf)));
+ memset(buf, 0x00, sizeof(buf));
+ ASSERT_EQ(0, memcmp(bl2.c_str()+sizeof(buf), buf, sizeof(buf)));
+}
+
+TEST_F(StriperTest, RemoveTest) {
+ char buf[128];
+ char buf2[sizeof(buf)];
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "RemoveTest", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_striper_remove(striper, "RemoveTest"));
+ ASSERT_EQ(-ENOENT, rados_striper_read(striper, "RemoveTest", buf2, sizeof(buf2), 0));
+}
+
+TEST_F(StriperTestPP, RemoveTestPP) {
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("RemoveTestPP", bl, sizeof(buf), 0));
+ ASSERT_EQ(0, striper.remove("RemoveTestPP"));
+ bufferlist bl2;
+ ASSERT_EQ(-ENOENT, striper.read("RemoveTestPP", &bl2, sizeof(buf), 0));
+}
+
+TEST_F(StriperTest, XattrsRoundTrip) {
+ char buf[128];
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "XattrsRoundTrip", buf, sizeof(buf), 0));
+ ASSERT_EQ(-ENODATA, rados_striper_getxattr(striper, "XattrsRoundTrip", "attr1", buf, sizeof(buf)));
+ ASSERT_EQ(0, rados_striper_setxattr(striper, "XattrsRoundTrip", "attr1", attr1_buf, sizeof(attr1_buf)));
+ ASSERT_EQ((int)sizeof(attr1_buf), rados_striper_getxattr(striper, "XattrsRoundTrip", "attr1", buf, sizeof(buf)));
+ ASSERT_EQ(0, memcmp(attr1_buf, buf, sizeof(attr1_buf)));
+}
+
+TEST_F(StriperTestPP, XattrsRoundTripPP) {
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("XattrsRoundTripPP", bl1, sizeof(buf), 0));
+ char attr1_buf[] = "foo bar baz";
+ bufferlist bl2;
+ ASSERT_EQ(-ENODATA, striper.getxattr("XattrsRoundTripPP", "attr1", bl2));
+ bufferlist bl3;
+ bl3.append(attr1_buf, sizeof(attr1_buf));
+ ASSERT_EQ(0, striper.setxattr("XattrsRoundTripPP", "attr1", bl3));
+ bufferlist bl4;
+ ASSERT_EQ((int)sizeof(attr1_buf), striper.getxattr("XattrsRoundTripPP", "attr1", bl4));
+ ASSERT_EQ(0, memcmp(bl4.c_str(), attr1_buf, sizeof(attr1_buf)));
+}
+
+TEST_F(StriperTest, RmXattr) {
+ char buf[128];
+ char attr1_buf[] = "foo bar baz";
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "RmXattr", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_striper_setxattr(striper, "RmXattr", "attr1", attr1_buf, sizeof(attr1_buf)));
+ ASSERT_EQ(0, rados_striper_rmxattr(striper, "RmXattr", "attr1"));
+ ASSERT_EQ(-ENODATA, rados_striper_getxattr(striper, "RmXattr", "attr1", buf, sizeof(buf)));
+}
+
+TEST_F(StriperTestPP, RmXattrPP) {
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("RmXattrPP", bl1, sizeof(buf), 0));
+ char attr1_buf[] = "foo bar baz";
+ bufferlist bl2;
+ bl2.append(attr1_buf, sizeof(attr1_buf));
+ ASSERT_EQ(0, striper.setxattr("RmXattrPP", "attr1", bl2));
+ ASSERT_EQ(0, striper.rmxattr("RmXattrPP", "attr1"));
+ bufferlist bl3;
+ ASSERT_EQ(-ENODATA, striper.getxattr("RmXattrPP", "attr1", bl3));
+}
+
+TEST_F(StriperTest, XattrIter) {
+ char buf[128];
+ char attr1_buf[] = "foo bar baz";
+ char attr2_buf[256];
+ for (size_t j = 0; j < sizeof(attr2_buf); ++j) {
+ attr2_buf[j] = j % 0xff;
+ }
+ memset(buf, 0xaa, sizeof(buf));
+ ASSERT_EQ(0, rados_striper_write(striper, "RmXattr", buf, sizeof(buf), 0));
+ ASSERT_EQ(0, rados_striper_setxattr(striper, "RmXattr", "attr1", attr1_buf, sizeof(attr1_buf)));
+ ASSERT_EQ(0, rados_striper_setxattr(striper, "RmXattr", "attr2", attr2_buf, sizeof(attr2_buf)));
+ rados_xattrs_iter_t iter;
+ ASSERT_EQ(0, rados_striper_getxattrs(striper, "RmXattr", &iter));
+ int num_seen = 0;
+ while (true) {
+ const char *name;
+ const char *val;
+ size_t len;
+ ASSERT_EQ(0, rados_striper_getxattrs_next(iter, &name, &val, &len));
+ if (name == NULL) {
+ break;
+ }
+ ASSERT_LT(num_seen, 2) << "Extra attribute : " << name;
+ if ((strcmp(name, "attr1") == 0) && (val != NULL) && (memcmp(val, attr1_buf, len) == 0)) {
+ num_seen++;
+ continue;
+ }
+ else if ((strcmp(name, "attr2") == 0) && (val != NULL) && (memcmp(val, attr2_buf, len) == 0)) {
+ num_seen++;
+ continue;
+ }
+ else {
+ ASSERT_EQ(0, 1) << "Unexpected attribute : " << name;;
+ }
+ }
+ rados_striper_getxattrs_end(iter);
+}
+
+TEST_F(StriperTestPP, XattrListPP) {
+ char buf[128];
+ memset(buf, 0xaa, sizeof(buf));
+ bufferlist bl1;
+ bl1.append(buf, sizeof(buf));
+ ASSERT_EQ(0, striper.write("RmXattrPP", bl1, sizeof(buf), 0));
+ char attr1_buf[] = "foo bar baz";
+ bufferlist bl2;
+ bl2.append(attr1_buf, sizeof(attr1_buf));
+ ASSERT_EQ(0, striper.setxattr("RmXattrPP", "attr1", bl2));
+ char attr2_buf[256];
+ for (size_t j = 0; j < sizeof(attr2_buf); ++j) {
+ attr2_buf[j] = j % 0xff;
+ }
+ bufferlist bl3;
+ bl3.append(attr2_buf, sizeof(attr2_buf));
+ ASSERT_EQ(0, striper.setxattr("RmXattrPP", "attr2", bl3));
+ std::map<std::string, bufferlist> attrset;
+ ASSERT_EQ(0, striper.getxattrs("RmXattrPP", attrset));
+ for (std::map<std::string, bufferlist>::iterator i = attrset.begin();
+ i != attrset.end(); ++i) {
+ if (i->first == string("attr1")) {
+ ASSERT_EQ(0, memcmp(i->second.c_str(), attr1_buf, sizeof(attr1_buf)));
+ }
+ else if (i->first == string("attr2")) {
+ ASSERT_EQ(0, memcmp(i->second.c_str(), attr2_buf, sizeof(attr2_buf)));
+ }
+ else {
+ ASSERT_EQ(0, 1) << "Unexpected attribute : " << i->first;
+ }
+ }
+}
diff --git a/src/test/libradosstriper/striping.cc b/src/test/libradosstriper/striping.cc
new file mode 100644
index 000000000..2de8b55f8
--- /dev/null
+++ b/src/test/libradosstriper/striping.cc
@@ -0,0 +1,329 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/compat.h"
+#include "include/types.h"
+#include "include/rados/librados.h"
+#include "include/rados/librados.hpp"
+#include "include/radosstriper/libradosstriper.h"
+#include "include/radosstriper/libradosstriper.hpp"
+#include "include/ceph_fs.h"
+#include "test/librados/test.h"
+#include "test/libradosstriper/TestCase.h"
+
+#include <string>
+#include <errno.h>
+using namespace librados;
+using namespace libradosstriper;
+
+class StriperTestRT : public StriperTestParam {
+public:
+ StriperTestRT() : StriperTestParam() {}
+protected:
+ char* getObjName(const std::string& soid, uint64_t nb)
+ {
+ char name[soid.size()+18];
+ sprintf(name, "%s.%016llx", soid.c_str(), (long long unsigned int)nb);
+ return strdup(name);
+ }
+
+ void checkObjectFromRados(const std::string& soid, bufferlist &bl,
+ uint64_t exp_stripe_unit, uint64_t exp_stripe_count,
+ uint64_t exp_object_size, size_t size)
+ {
+ checkObjectFromRados(soid, bl, exp_stripe_unit, exp_stripe_count, exp_object_size, size, size);
+ }
+
+ void checkObjectFromRados(const std::string& soid, bufferlist &bl,
+ uint64_t exp_stripe_unit, uint64_t exp_stripe_count,
+ uint64_t exp_object_size, size_t size,
+ size_t actual_size_if_sparse)
+ {
+ // checking first object's rados xattrs
+ bufferlist xattrbl;
+ char* firstOid = getObjName(soid, 0);
+ ASSERT_LT(0, ioctx.getxattr(firstOid, "striper.layout.stripe_unit", xattrbl));
+ std::string s_xattr(xattrbl.c_str(), xattrbl.length()); // adds 0 byte at the end
+ uint64_t stripe_unit = strtoll(s_xattr.c_str(), NULL, 10);
+ ASSERT_LT((unsigned)0, stripe_unit);
+ ASSERT_EQ(stripe_unit, exp_stripe_unit);
+ xattrbl.clear();
+ ASSERT_LT(0, ioctx.getxattr(firstOid, "striper.layout.stripe_count", xattrbl));
+ s_xattr = std::string(xattrbl.c_str(), xattrbl.length()); // adds 0 byte at the end
+ uint64_t stripe_count = strtoll(s_xattr.c_str(), NULL, 10);
+ ASSERT_LT(0U, stripe_count);
+ ASSERT_EQ(stripe_count, exp_stripe_count);
+ xattrbl.clear();
+ ASSERT_LT(0, ioctx.getxattr(firstOid, "striper.layout.object_size", xattrbl));
+ s_xattr = std::string(xattrbl.c_str(), xattrbl.length()); // adds 0 byte at the end
+ uint64_t object_size = strtoll(s_xattr.c_str(), NULL, 10);
+ ASSERT_EQ(object_size, exp_object_size);
+ xattrbl.clear();
+ ASSERT_LT(0, ioctx.getxattr(firstOid, "striper.size", xattrbl));
+ s_xattr = std::string(xattrbl.c_str(), xattrbl.length()); // adds 0 byte at the end
+ uint64_t xa_size = strtoll(s_xattr.c_str(), NULL, 10);
+ ASSERT_EQ(xa_size, size);
+ // checking object content from rados point of view
+ // we will go stripe by stripe, read the content of each of them and
+ // check with expectations
+ uint64_t stripe_per_object = object_size / stripe_unit;
+ uint64_t stripe_per_objectset = stripe_per_object * stripe_count;
+ uint64_t nb_stripes_in_object = (size+stripe_unit-1)/stripe_unit;
+ for (uint64_t stripe_nb = 0;
+ stripe_nb < nb_stripes_in_object;
+ stripe_nb++) {
+ // find out where this stripe is stored
+ uint64_t objectset = stripe_nb / stripe_per_objectset;
+ uint64_t stripe_in_object_set = stripe_nb % stripe_per_objectset;
+ uint64_t object_in_set = stripe_in_object_set % stripe_count;
+ uint64_t stripe_in_object = stripe_in_object_set / stripe_count;
+ uint64_t object_nb = objectset * stripe_count + object_in_set;
+ uint64_t start = stripe_in_object * stripe_unit;
+ uint64_t len = stripe_unit;
+ if (stripe_nb == nb_stripes_in_object-1 and size % stripe_unit != 0) {
+ len = size % stripe_unit;
+ }
+ // handle case of sparse object (can only be sparse at the end in our tests)
+ if (actual_size_if_sparse < size and
+ ((actual_size_if_sparse+stripe_unit-1)/stripe_unit)-1 == stripe_nb) {
+ len = actual_size_if_sparse % stripe_unit;
+ if (0 == len) len = stripe_unit;
+ }
+ bufferlist stripe_data;
+ // check object content
+ char* oid = getObjName(soid, object_nb);
+ int rc = ioctx.read(oid, stripe_data, len, start);
+ if (actual_size_if_sparse < size and
+ (actual_size_if_sparse+stripe_unit-1)/stripe_unit <= stripe_nb) {
+ // sparse object case : the stripe does not exist, but the rados object may
+ uint64_t object_start = (object_in_set + objectset*stripe_per_objectset) * stripe_unit;
+ if (actual_size_if_sparse <= object_start) {
+ ASSERT_EQ(rc, -ENOENT);
+ } else {
+ ASSERT_EQ(rc, 0);
+ }
+ } else {
+ ASSERT_EQ((uint64_t)rc, len);
+ bufferlist original_data;
+ original_data.substr_of(bl, stripe_nb*stripe_unit, len);
+ ASSERT_EQ(0, memcmp(original_data.c_str(), stripe_data.c_str(), len));
+ }
+ free(oid);
+ }
+ // checking rados object sizes; we go object by object
+ uint64_t nb_full_object_sets = nb_stripes_in_object / stripe_per_objectset;
+ uint64_t nb_extra_objects = nb_stripes_in_object % stripe_per_objectset;
+ if (nb_extra_objects > stripe_count) nb_extra_objects = stripe_count;
+ uint64_t nb_objects = nb_full_object_sets * stripe_count + nb_extra_objects;
+ for (uint64_t object_nb = 0; object_nb < nb_objects; object_nb++) {
+ uint64_t rados_size;
+ time_t mtime;
+ char* oid = getObjName(soid, object_nb);
+ uint64_t nb_full_object_set = object_nb / stripe_count;
+ uint64_t object_index_in_set = object_nb % stripe_count;
+ uint64_t object_start_stripe = nb_full_object_set * stripe_per_objectset + object_index_in_set;
+ uint64_t object_start_off = object_start_stripe * stripe_unit;
+ if (actual_size_if_sparse < size and actual_size_if_sparse <= object_start_off) {
+ ASSERT_EQ(-ENOENT, ioctx.stat(oid, &rados_size, &mtime));
+ } else {
+ ASSERT_EQ(0, ioctx.stat(oid, &rados_size, &mtime));
+ uint64_t offset;
+ uint64_t stripe_size = stripe_count * stripe_unit;
+ uint64_t set_size = stripe_count * object_size;
+ uint64_t len = 0;
+ for (offset = object_start_off;
+ (offset < (object_start_off) + set_size) && (offset < actual_size_if_sparse);
+ offset += stripe_size) {
+ if (offset + stripe_unit > actual_size_if_sparse) {
+ len += actual_size_if_sparse-offset;
+ } else {
+ len += stripe_unit;
+ }
+ }
+ ASSERT_EQ(len, rados_size);
+ }
+ free(oid);
+ }
+ // check we do not have an extra object behind
+ uint64_t rados_size;
+ time_t mtime;
+ char* oid = getObjName(soid, nb_objects);
+ ASSERT_EQ(-ENOENT, ioctx.stat(oid, &rados_size, &mtime));
+ free(oid);
+ free(firstOid);
+ }
+};
+
+TEST_P(StriperTestRT, StripedRoundtrip) {
+ // get striping parameters and apply them
+ TestData testData = GetParam();
+ ASSERT_EQ(0, striper.set_object_layout_stripe_unit(testData.stripe_unit));
+ ASSERT_EQ(0, striper.set_object_layout_stripe_count(testData.stripe_count));
+ ASSERT_EQ(0, striper.set_object_layout_object_size(testData.object_size));
+ std::ostringstream oss;
+ oss << "StripedRoundtrip_" << testData.stripe_unit << "_"
+ << testData.stripe_count << "_" << testData.object_size
+ << "_" << testData.size;
+ std::string soid = oss.str();
+ // writing striped data
+ std::unique_ptr<char[]> buf1;
+ bufferlist bl1;
+ {
+ SCOPED_TRACE("Writing initial object");
+ buf1 = std::make_unique<char[]>(testData.size);
+ for (unsigned int i = 0; i < testData.size; i++) buf1[i] = 13*((unsigned char)i);
+ bl1.append(buf1.get(), testData.size);
+ ASSERT_EQ(0, striper.write(soid, bl1, testData.size, 0));
+ // checking object state from Rados point of view
+ ASSERT_NO_FATAL_FAILURE(checkObjectFromRados(soid, bl1, testData.stripe_unit,
+ testData.stripe_count, testData.object_size,
+ testData.size));
+ }
+ // adding more data to object and checking again
+ std::unique_ptr<char[]> buf2;
+ bufferlist bl2;
+ {
+ SCOPED_TRACE("Testing append");
+ buf2 = std::make_unique<char[]>(testData.size);
+ for (unsigned int i = 0; i < testData.size; i++) buf2[i] = 17*((unsigned char)i);
+ bl2.append(buf2.get(), testData.size);
+ ASSERT_EQ(0, striper.append(soid, bl2, testData.size));
+ bl1.append(buf2.get(), testData.size);
+ ASSERT_NO_FATAL_FAILURE(checkObjectFromRados(soid, bl1, testData.stripe_unit,
+ testData.stripe_count, testData.object_size,
+ testData.size*2));
+ }
+ // truncating to half original size and checking again
+ {
+ SCOPED_TRACE("Testing trunc to truncate object");
+ ASSERT_EQ(0, striper.trunc(soid, testData.size/2));
+ ASSERT_NO_FATAL_FAILURE(checkObjectFromRados(soid, bl1, testData.stripe_unit,
+ testData.stripe_count, testData.object_size,
+ testData.size/2));
+ }
+ // truncating back to original size and checking again (especially for 0s)
+ {
+ SCOPED_TRACE("Testing trunc to extend object with 0s");
+ ASSERT_EQ(0, striper.trunc(soid, testData.size));
+ bufferlist bl3;
+ bl3.substr_of(bl1, 0, testData.size/2);
+ bl3.append_zero(testData.size - testData.size/2);
+ ASSERT_NO_FATAL_FAILURE(checkObjectFromRados(soid, bl3, testData.stripe_unit,
+ testData.stripe_count, testData.object_size,
+ testData.size, testData.size/2));
+ }
+ {
+ SCOPED_TRACE("Testing write_full");
+ // using write_full and checking again
+ ASSERT_EQ(0, striper.write_full(soid, bl2));
+ checkObjectFromRados(soid, bl2, testData.stripe_unit,
+ testData.stripe_count, testData.object_size,
+ testData.size);
+ }
+ {
+ SCOPED_TRACE("Testing standard remove");
+ // call remove
+ ASSERT_EQ(0, striper.remove(soid));
+ // check that the removal was successful
+ uint64_t size;
+ time_t mtime;
+ for (uint64_t object_nb = 0;
+ object_nb < testData.size*2/testData.object_size + testData.stripe_count;
+ object_nb++) {
+ char* oid = getObjName(soid, object_nb);
+ ASSERT_EQ(-ENOENT, ioctx.stat(oid, &size, &mtime));
+ free(oid);
+ }
+ }
+ {
+ SCOPED_TRACE("Testing remove when no object size");
+ // recreate object
+ ASSERT_EQ(0, striper.write(soid, bl1, testData.size*2, 0));
+ // remove the object size attribute from the striped object
+ char* firstOid = getObjName(soid, 0);
+ ASSERT_EQ(0, ioctx.rmxattr(firstOid, "striper.size"));
+ free(firstOid);
+ // check that stat fails
+ uint64_t size;
+ time_t mtime;
+ ASSERT_EQ(-ENODATA, striper.stat(soid, &size, &mtime));
+ // call remove
+ ASSERT_EQ(0, striper.remove(soid));
+ // check that the removal was successful
+ for (uint64_t object_nb = 0;
+ object_nb < testData.size*2/testData.object_size + testData.stripe_count;
+ object_nb++) {
+ char* oid = getObjName(soid, object_nb);
+ ASSERT_EQ(-ENOENT, ioctx.stat(oid, &size, &mtime));
+ free(oid);
+ }
+ }
+}
+
+const TestData simple_stripe_schemes[] = {
+ // stripe_unit, stripe_count, object_size, size
+ {CEPH_MIN_STRIPE_UNIT, 5, CEPH_MIN_STRIPE_UNIT, 2},
+ {CEPH_MIN_STRIPE_UNIT, 5, CEPH_MIN_STRIPE_UNIT, CEPH_MIN_STRIPE_UNIT},
+ {CEPH_MIN_STRIPE_UNIT, 5, CEPH_MIN_STRIPE_UNIT, CEPH_MIN_STRIPE_UNIT-1},
+ {CEPH_MIN_STRIPE_UNIT, 5, CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT},
+ {CEPH_MIN_STRIPE_UNIT, 5, CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT},
+ {CEPH_MIN_STRIPE_UNIT, 5, CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT-1},
+ {CEPH_MIN_STRIPE_UNIT, 5, CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT-1},
+ {CEPH_MIN_STRIPE_UNIT, 5, CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 5, CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 5, 3*CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 5, 3*CEPH_MIN_STRIPE_UNIT, 8*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 5, 3*CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 5, 3*CEPH_MIN_STRIPE_UNIT, 15*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 5, 3*CEPH_MIN_STRIPE_UNIT, 25*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 5, 3*CEPH_MIN_STRIPE_UNIT, 45*CEPH_MIN_STRIPE_UNIT+100},
+ {262144, 5, 262144, 2},
+ {262144, 5, 262144, 262144},
+ {262144, 5, 262144, 262144-1},
+ {262144, 5, 262144, 2*262144},
+ {262144, 5, 262144, 12*262144},
+ {262144, 5, 262144, 2*262144-1},
+ {262144, 5, 262144, 12*262144-1},
+ {262144, 5, 262144, 2*262144+100},
+ {262144, 5, 262144, 12*262144+100},
+ {262144, 5, 3*262144, 2*262144+100},
+ {262144, 5, 3*262144, 8*262144+100},
+ {262144, 5, 3*262144, 12*262144+100},
+ {262144, 5, 3*262144, 15*262144+100},
+ {262144, 5, 3*262144, 25*262144+100},
+ {262144, 5, 3*262144, 45*262144+100},
+ {CEPH_MIN_STRIPE_UNIT, 1, CEPH_MIN_STRIPE_UNIT, 2},
+ {CEPH_MIN_STRIPE_UNIT, 1, CEPH_MIN_STRIPE_UNIT, CEPH_MIN_STRIPE_UNIT},
+ {CEPH_MIN_STRIPE_UNIT, 1, CEPH_MIN_STRIPE_UNIT, CEPH_MIN_STRIPE_UNIT-1},
+ {CEPH_MIN_STRIPE_UNIT, 1, CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT},
+ {CEPH_MIN_STRIPE_UNIT, 1, CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT},
+ {CEPH_MIN_STRIPE_UNIT, 1, CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT-1},
+ {CEPH_MIN_STRIPE_UNIT, 1, CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT-1},
+ {CEPH_MIN_STRIPE_UNIT, 1, CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 1, CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 1, 3*CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 1, 3*CEPH_MIN_STRIPE_UNIT, 8*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 1, 3*CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 1, 3*CEPH_MIN_STRIPE_UNIT, 15*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 1, 3*CEPH_MIN_STRIPE_UNIT, 25*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 1, 3*CEPH_MIN_STRIPE_UNIT, 45*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 50, CEPH_MIN_STRIPE_UNIT, 2},
+ {CEPH_MIN_STRIPE_UNIT, 50, CEPH_MIN_STRIPE_UNIT, CEPH_MIN_STRIPE_UNIT},
+ {CEPH_MIN_STRIPE_UNIT, 50, CEPH_MIN_STRIPE_UNIT, CEPH_MIN_STRIPE_UNIT-1},
+ {CEPH_MIN_STRIPE_UNIT, 50, CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT},
+ {CEPH_MIN_STRIPE_UNIT, 50, CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT},
+ {CEPH_MIN_STRIPE_UNIT, 50, CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT-1},
+ {CEPH_MIN_STRIPE_UNIT, 50, CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT-1},
+ {CEPH_MIN_STRIPE_UNIT, 50, CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 50, CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 50, 3*CEPH_MIN_STRIPE_UNIT, 2*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 50, 3*CEPH_MIN_STRIPE_UNIT, 8*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 50, 3*CEPH_MIN_STRIPE_UNIT, 12*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 50, 3*CEPH_MIN_STRIPE_UNIT, 15*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 50, 3*CEPH_MIN_STRIPE_UNIT, 25*CEPH_MIN_STRIPE_UNIT+100},
+ {CEPH_MIN_STRIPE_UNIT, 50, 3*CEPH_MIN_STRIPE_UNIT, 45*CEPH_MIN_STRIPE_UNIT+100}
+};
+
+INSTANTIATE_TEST_SUITE_P(SimpleStriping,
+ StriperTestRT,
+ ::testing::ValuesIn(simple_stripe_schemes));