diff options
Diffstat (limited to '')
71 files changed, 31321 insertions, 0 deletions
diff --git a/src/test/librados/CMakeLists.txt b/src/test/librados/CMakeLists.txt new file mode 100644 index 000000000..e9338bbd4 --- /dev/null +++ b/src/test/librados/CMakeLists.txt @@ -0,0 +1,199 @@ +# 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} ${OPENLDAP_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} Boost::coroutine Boost::context) + +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_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) + +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 + 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} ${OPENLDAP_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} ${OPENLDAP_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..1066587b5 --- /dev/null +++ b/src/test/librados/TestCase.cc @@ -0,0 +1,190 @@ +// -*- 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/TestCase.h" +#include "include/scope_guard.h" + + +std::string RadosTestNS::pool_name; +rados_t RadosTestNS::s_cluster = NULL; + + +void RadosTestNS::SetUpTestCase() +{ + pool_name = get_temp_pool_name(); + 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 requires; + ASSERT_EQ(0, rados_ioctx_pool_requires_alignment2(ioctx, &requires)); + ASSERT_FALSE(requires); +} + +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() +{ + pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_ec_pool(pool_name, &s_cluster)); +} + +void RadosTestECNS::TearDownTestCase() +{ + ASSERT_EQ(0, destroy_one_ec_pool(pool_name, &s_cluster)); +} + +void RadosTestECNS::SetUp() +{ + cluster = RadosTestECNS::s_cluster; + ASSERT_EQ(0, rados_ioctx_create(cluster, pool_name.c_str(), &ioctx)); + int requires; + ASSERT_EQ(0, rados_ioctx_pool_requires_alignment2(ioctx, &requires)); + ASSERT_TRUE(requires); + ASSERT_EQ(0, rados_ioctx_pool_required_alignment2(ioctx, &alignment)); + ASSERT_NE(0U, alignment); +} + +void RadosTestECNS::TearDown() +{ + if (cleanup) + cleanup_all_objects(ioctx); + rados_ioctx_destroy(ioctx); +} + +std::string RadosTest::pool_name; +rados_t RadosTest::s_cluster = NULL; + +void RadosTest::SetUpTestCase() +{ + pool_name = get_temp_pool_name(); + 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 requires; + ASSERT_EQ(0, rados_ioctx_pool_requires_alignment2(ioctx, &requires)); + ASSERT_FALSE(requires); +} + +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() +{ + pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_ec_pool(pool_name, &s_cluster)); +} + +void RadosTestEC::TearDownTestCase() +{ + ASSERT_EQ(0, destroy_one_ec_pool(pool_name, &s_cluster)); +} + +void RadosTestEC::SetUp() +{ + 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 requires; + ASSERT_EQ(0, rados_ioctx_pool_requires_alignment2(ioctx, &requires)); + ASSERT_TRUE(requires); + ASSERT_EQ(0, rados_ioctx_pool_required_alignment2(ioctx, &alignment)); + ASSERT_NE(0U, alignment); +} + +void RadosTestEC::TearDown() +{ + 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..2ffff1c34 --- /dev/null +++ b/src/test/librados/aio.cc @@ -0,0 +1,1642 @@ +#include <errno.h> +#include <fcntl.h> +#include <string> +#include <sstream> +#include <utility> +#include <boost/scoped_ptr.hpp> + +#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" + +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; + m_pool_name = get_temp_pool_name(); + 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 = init_le32(-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, 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; + m_pool_name = get_temp_pool_name(); + 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) { + 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) { + 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) { + 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) { + 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) { + 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 requires; + ASSERT_EQ(0, rados_ioctx_pool_requires_alignment2(test_data.m_ioctx, &requires)); + ASSERT_NE(0, requires); + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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..5df248869 --- /dev/null +++ b/src/test/librados/aio_cxx.cc @@ -0,0 +1,2277 @@ +#include <errno.h> +#include <fcntl.h> +#include <sstream> +#include <string> +#include <utility> +#include <boost/scoped_ptr.hpp> + +#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 "test_cxx.h" + +using namespace librados; +using std::pair; +using std::ostringstream; + +class AioTestDataPP +{ +public: + AioTestDataPP() + : m_init(false) + { + } + + ~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; + + m_pool_name = get_temp_pool_name(); + 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_init = true; + return ""; + } + + Rados m_cluster; + IoCtx m_ioctx; + std::string m_pool_name; + bool m_init; +}; + +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("foo", aio_completion.get(), bl, UINT_MAX, 0)); + ASSERT_EQ(-E2BIG, test_data.m_ioctx.aio_append("foo", 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()); + string p = get_temp_pool_name(); + 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("foo" + 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("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()); + } + + { + 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("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()); + } +} + +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("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()); +} + +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("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()); + 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("foo", 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("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()); + 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("foo", 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; + 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); + + 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("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()); + 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("foo", 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("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()); + + 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("foo", 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("foo", 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("foo", 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("foo", 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("foo", 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("foo", 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("foo", 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("foo", 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("foo", 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("foo", bl1, sizeof(buf))); + bufferlist bl2; + bl2.append(attr1_buf, sizeof(attr1_buf)); + ASSERT_EQ(0, test_data.m_ioctx.setxattr("foo", attr1, bl2)); + bufferlist bl3; + bl3.append(attr2_buf, sizeof(attr2_buf)); + ASSERT_EQ(0, test_data.m_ioctx.setxattr("foo", 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("foo", 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("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()); + 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("foo", 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("foo", 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("foo", 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("foo", 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("foo", 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("foo", 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("foo", 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("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()); + 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("foo", 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("foo", 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; + 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); + + 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("foo", 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("foo", 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("foo", 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; + 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); + + 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("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()); + 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("foo", 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("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()); + 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("foo", 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, 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("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()); + 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("foo", 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("foo", 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("foo", 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("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()); + 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("foo", 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; + 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); + + 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("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()); + + 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("foo", 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("foo", 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("foo", "TestLock", "Cookie", "", NULL, 0)); + auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()}; + ASSERT_EQ(0, test_data.m_ioctx.aio_unlock("foo", "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("foo", "TestLock", "Cookie", "", NULL, 0)); +} + +class AioTestDataECPP +{ +public: + AioTestDataECPP() + : m_init(false) + {} + + ~AioTestDataECPP() + { + if (m_init) { + m_ioctx.close(); + destroy_one_ec_pool_pp(m_pool_name, m_cluster); + } + } + + std::string init() + { + int ret; + m_pool_name = get_temp_pool_name(); + 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_init = true; + return ""; + } + + Rados m_cluster; + IoCtx m_ioctx; + std::string m_pool_name; + bool m_init; +}; + +// EC test cases +TEST(LibRadosAioEC, SimpleWritePP) { + 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("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()); + } + + { + 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("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()); + } +} + +TEST(LibRadosAioEC, WaitForSafePP) { + 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("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()); +} + +TEST(LibRadosAioEC, RoundTripPP) { + 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("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()); + 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("foo", 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) { + 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("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()); + 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("foo", 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) +{ + 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); + + 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("foo", 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("foo", 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("foo", 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("foo", bl1, sizeof(buf))); + auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()}; + ASSERT_EQ(0, test_data.m_ioctx.aio_remove("foo", 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("foo", bl2, sizeof(buf), 0)); +} + +TEST(LibRadosAioEC, RoundTripSparseReadPP) { + 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("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()); + + 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("foo", 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) { + AioTestDataECPP test_data; + ASSERT_EQ("", test_data.init()); + auto my_completion = std::unique_ptr<AioCompletion>{Rados::aio_create_completion()}; + ASSERT_TRUE(my_completion); + bool requires; + ASSERT_EQ(0, test_data.m_ioctx.pool_requires_alignment2(&requires)); + ASSERT_TRUE(requires); + 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("foo", 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("foo", 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("foo", 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("foo", 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) { + 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("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()); + 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("foo", 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) { + 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("foo", 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("foo", 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) { + 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) { + 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("foo", 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("foo", 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) { + 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("foo", 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("foo", 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) { + 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("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()); + 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("foo", 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("foo", 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) +{ + 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); + + 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) { + 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("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()); + 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("foo", 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) { + 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("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()); + 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("foo", 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) { + 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("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()); + 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("foo", 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("foo", 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("foo", 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) { + 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("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()); + 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("foo", 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) { + Rados cluster; + std::string pool_name = get_temp_pool_name(); + 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) { + 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("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()); + + 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("foo", 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("foo", 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("foo", my_completion2.get())); + ASSERT_EQ(0, test_data.m_ioctx.aio_write("foo", 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("foo", 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("foo", 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("foo", 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("foo", 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; + 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); + + 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); +} + +// 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..9dd6b00fe --- /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" + +#define BOOST_COROUTINES_NO_DEPRECATION_WARNING +#include <boost/range/begin.hpp> +#include <boost/range/end.hpp> +#include <boost/asio/spawn.hpp> +#include <boost/asio/use_future.hpp> + +#define dout_subsys ceph_subsys_rados +#define dout_context g_ceph_context + +// 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 = [&] (boost::asio::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()); + }; + boost::asio::spawn(service, success_cr); + + auto failure_cr = [&] (boost::asio::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); + }; + boost::asio::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 = [&] (boost::asio::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()); + }; + boost::asio::spawn(service, success_cr); + + auto failure_cr = [&] (boost::asio::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); + }; + boost::asio::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 = [&] (boost::asio::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()); + }; + boost::asio::spawn(service, success_cr); + + auto failure_cr = [&] (boost::asio::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); + }; + boost::asio::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 = [&] (boost::asio::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); + }; + boost::asio::spawn(service, success_cr); + + auto failure_cr = [&] (boost::asio::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); + }; + boost::asio::spawn(service, failure_cr); + + service.run(); +} + +int main(int argc, char **argv) +{ + vector<const char*> args; + argv_to_vec(argc, (const char **)argv, args); + 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..a6cb3918b --- /dev/null +++ b/src/test/librados/c_read_operations.cc @@ -0,0 +1,848 @@ +// -*- 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 = init_le64(-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 = init_le32(-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 = init_le32(-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 = init_le32(-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; + 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); + + write_object(); + + op = rados_create_read_op(); + rados_read_op_stat(op, &size, NULL, &rval); + EXPECT_EQ(0, rados_read_op_operate(op, ioctx, obj, 0)); + EXPECT_EQ(0, rval); + EXPECT_EQ(len, size); + 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, 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/cmd.cc b/src/test/librados/cmd.cc new file mode 100644 index 000000000..3da398c12 --- /dev/null +++ b/src/test/librados/cmd.cc @@ -0,0 +1,227 @@ +// -*- 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::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/io.cc b/src/test/librados/io.cc new file mode 100644 index 000000000..9e801dcd4 --- /dev/null +++ b/src/test/librados/io.cc @@ -0,0 +1,442 @@ +// -*- 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" + +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]; + 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 = init_le32(-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, 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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..64d03380b --- /dev/null +++ b/src/test/librados/io_cxx.cc @@ -0,0 +1,954 @@ +// -*- 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" + +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); + } +} + +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) { + 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) { + 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) { + 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) { + 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) +{ + 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) { + 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) { + 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) +{ + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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..829890c1a --- /dev/null +++ b/src/test/librados/list.cc @@ -0,0 +1,542 @@ +// -*- 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> + +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) { + 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) { + 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) { + 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. + 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. + ASSERT_TRUE(set_pg_num(&s_cluster, pool_name, 11).empty()); + ASSERT_TRUE(set_pgp_num(&s_cluster, pool_name, 11).empty()); + + 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..45368b39c --- /dev/null +++ b/src/test/librados/list_cxx.cc @@ -0,0 +1,773 @@ +// -*- 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" + +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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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..53ed300e7 --- /dev/null +++ b/src/test/librados/lock.cc @@ -0,0 +1,227 @@ +#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> + +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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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..3007b2268 --- /dev/null +++ b/src/test/librados/lock_cxx.cc @@ -0,0 +1,193 @@ +#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" + +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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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..9c052a5d9 --- /dev/null +++ b/src/test/librados/misc.cc @@ -0,0 +1,354 @@ +// -*- 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 librados; +using std::map; +using std::ostringstream; +using std::string; + +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(0, rados_conf_set(cluster, "client_mount_timeout", "1s")); + ASSERT_EQ(0, rados_conf_set(cluster, "debug_monc", "20")); + ASSERT_EQ(0, rados_conf_set(cluster, "debug_ms", "1")); + ASSERT_EQ(0, rados_conf_set(cluster, "log_to_stderr", "true")); + + ASSERT_EQ(-ENOTCONN, rados_monitor_log(cluster, "error", + test_rados_log_cb, NULL)); + + // try this a few times; sometimes we don't schedule fast enough for the + // cond to time out + int r; + for (unsigned i=0; i<16; ++i) { + cout << i << std::endl; + r = rados_connect(cluster); + if (r < 0) + break; // yay, we timed out + // try again + rados_shutdown(cluster); + ASSERT_EQ(0, rados_create(&cluster, NULL)); + } + ASSERT_NE(0, r); + + rados_shutdown(cluster); +} + +TEST(LibRadosMiscPool, PoolCreationRace) { + rados_t cluster_a, cluster_b; + + char *id = getenv("CEPH_CLIENT_ID"); + if (id) + std::cerr << "Client id is: " << id << std::endl; + + ASSERT_EQ(0, rados_create(&cluster_a, NULL)); + ASSERT_EQ(0, rados_conf_read_file(cluster_a, NULL)); + // kludge: i want to --log-file foo and only get cluster b + //ASSERT_EQ(0, rados_conf_parse_env(cluster_a, NULL)); + ASSERT_EQ(0, rados_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..e7ad1ec59 --- /dev/null +++ b/src/test/librados/misc_cxx.cc @@ -0,0 +1,915 @@ +// -*- 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" + +using namespace librados; +using std::map; +using std::ostringstream; +using std::string; + +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) { + 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() { + pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_ec_pool_pp(pool_name, s_cluster)); + src_pool_name = get_temp_pool_name(); + ASSERT_EQ(0, s_cluster.pool_create(src_pool_name.c_str())); + + librados::IoCtx ioctx; + ASSERT_EQ(0, s_cluster.ioctx_create(pool_name.c_str(), ioctx)); + ioctx.application_enable("rados", true); + + librados::IoCtx src_ioctx; + ASSERT_EQ(0, s_cluster.ioctx_create(src_pool_name.c_str(), src_ioctx)); + src_ioctx.application_enable("rados", true); + } + static void TearDownTestCase() { + ASSERT_EQ(0, s_cluster.pool_delete(src_pool_name.c_str())); + ASSERT_EQ(0, destroy_one_ec_pool_pp(pool_name, s_cluster)); + } + static std::string src_pool_name; + + void SetUp() override { + RadosTestECPP::SetUp(); + ASSERT_EQ(0, cluster.ioctx_create(src_pool_name.c_str(), src_ioctx)); + src_ioctx.set_namespace(nspace); + } + void TearDown() override { + // wait for maps to settle before next test + cluster.wait_for_latest_osdmap(); + + RadosTestECPP::TearDown(); + + cleanup_default_namespace(src_ioctx); + cleanup_namespace(src_ioctx, nspace); + + src_ioctx.close(); + } + + librados::IoCtx src_ioctx; +}; +std::string LibRadosTwoPoolsECPP::src_pool_name; + +//copy_from between ecpool and no-ecpool. +TEST_F(LibRadosTwoPoolsECPP, CopyFrom) { + bufferlist z; + z.append_zero(4194304*2); + bufferlist b; + b.append("copyfrom"); + + // create big object w/ omapheader + { + ASSERT_EQ(0, src_ioctx.write_full("foo", z)); + ASSERT_EQ(0, src_ioctx.omap_set_header("foo", b)); + version_t uv = src_ioctx.get_last_version(); + ObjectWriteOperation op; + op.copy_from("foo", src_ioctx, uv, 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) { + 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) { + 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..90c7bdac5 --- /dev/null +++ b/src/test/librados/op_speed.cc @@ -0,0 +1,22 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -* +// vim: ts=8 sw=2 smarttab + +#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..19a170ed2 --- /dev/null +++ b/src/test/librados/pool.cc @@ -0,0 +1,184 @@ +#include <errno.h> +#include <vector> +#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) { + 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..223dc967b --- /dev/null +++ b/src/test/librados/service.cc @@ -0,0 +1,202 @@ +#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" +#include <sys/resource.h> + +#include <mutex> +#include <condition_variable> +#include <algorithm> +#include <thread> +#include <errno.h> +#include "gtest/gtest.h" +#include "test/unit.cc" + +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 = NULL; + + 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_NE(-1, asprintf(&metadata_buf, "%s%c%s%c", + "foo", '\0', "bar", '\0')); + } else if (i == 1) { + ASSERT_NE(-1, asprintf(&metadata_buf, "%s%c%s%c", + "daemon_type", '\0', "portal", '\0')); + } else if (i == 2) { + ASSERT_NE(-1, asprintf(&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_NE(-1, asprintf(&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; + + // 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(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); + ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &rold), 0); +} + +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..40869f0f8 --- /dev/null +++ b/src/test/librados/service_cxx.cc @@ -0,0 +1,104 @@ +#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 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..ec8e53427 --- /dev/null +++ b/src/test/librados/snapshots.cc @@ -0,0 +1,301 @@ +#include "include/rados.h" +#include "test/librados/test.h" +#include "test/librados/TestCase.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)); + 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)); + ASSERT_EQ(0, rados_write(ioctx, "foo", buf2, sizeof(buf2), 0)); + 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")); +} + +// EC testing +TEST_F(LibRadosSnapshotsEC, 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(LibRadosSnapshotsEC, 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(LibRadosSnapshotsEC, 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(LibRadosSnapshotsEC, 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(LibRadosSnapshotsSelfManagedEC, 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()); + 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) { + 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..dee17ddb8 --- /dev/null +++ b/src/test/librados/snapshots_cxx.cc @@ -0,0 +1,732 @@ +#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" + +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) { + 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) { + 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, 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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/stat.cc b/src/test/librados/stat.cc new file mode 100644 index 000000000..fd6677427 --- /dev/null +++ b/src/test/librados/stat.cc @@ -0,0 +1,122 @@ +#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" + +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; + time_t mtime; + 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, 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; + time_t mtime; + 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) { + char buf[128]; + memset(buf, 0xcc, sizeof(buf)); + ASSERT_EQ(0, rados_write(ioctx, "foo", buf, sizeof(buf), 0)); + uint64_t size; + time_t mtime; + 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) { + 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; + time_t mtime; + 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) { + struct rados_cluster_stat_t result; + ASSERT_EQ(0, rados_cluster_stat(cluster, &result)); +} + +TEST_F(LibRadosStatEC, 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)); +} diff --git a/src/test/librados/stat_cxx.cc b/src/test/librados/stat_cxx.cc new file mode 100644 index 000000000..d0d0116df --- /dev/null +++ b/src/test/librados/stat_cxx.cc @@ -0,0 +1,163 @@ +#include "gtest/gtest.h" + +#include "include/rados/librados.hpp" + +#include "test/librados/test_cxx.h" +#include "test/librados/testcase_cxx.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) { + 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) { + cluster_stat_t cstat; + ASSERT_EQ(0, cluster.cluster_stat(cstat)); +} + +TEST_F(LibRadosStatECPP, 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(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) { + 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..fdfbc5419 --- /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_ruleset(rados_t *cluster, + const std::string &ruleset, + std::ostream &oss) +{ + char *cmd[2]; + std::string tmp = ("{\"prefix\": \"osd crush rule rm\", \"name\":\"" + + ruleset + "\"}"); + 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 " + ruleset + " failed with error " << ret; + return ret; +} + +int destroy_ec_profile_and_ruleset(rados_t *cluster, + const std::string &ruleset, + std::ostream &oss) +{ + int ret; + ret = destroy_ec_profile(cluster, ruleset, oss); + if (ret) + return ret; + return destroy_ruleset(cluster, ruleset, 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_ruleset(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_ruleset(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..c17b710d1 --- /dev/null +++ b/src/test/librados/test_common.cc @@ -0,0 +1,165 @@ +// -*- 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" + +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..9349aece8 --- /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_ruleset_pp(Rados &cluster, + const std::string &ruleset, + std::ostream &oss) +{ + bufferlist inbl; + int ret = cluster.mon_command("{\"prefix\": \"osd crush rule rm\", \"name\":\"" + + ruleset + "\"}", inbl, NULL, NULL); + if (ret) + oss << "mon_command: osd crush rule rm " + ruleset + " 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_ruleset_pp(Rados &cluster, + const std::string &ruleset, + std::ostream &oss) +{ + int ret; + ret = destroy_ec_profile_pp(cluster, ruleset, oss); + if (ret) + return ret; + return destroy_ruleset_pp(cluster, ruleset, 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_ruleset_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_ruleset_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..6f3747e7b --- /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(1200); + } + ~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..b660014be --- /dev/null +++ b/src/test/librados/testcase_cxx.cc @@ -0,0 +1,391 @@ +// -*- 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 "test_cxx.h" +#include "test_shared.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() +{ + pool_name = get_temp_pool_name(); + 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 requires; + ASSERT_EQ(0, ioctx.pool_requires_alignment2(&requires)); + ASSERT_FALSE(requires); +} + +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() +{ + pool_name = get_temp_pool_name(); + 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 (strcmp(GetParam(), "cache") == 0 && cache_pool_name.empty()) { + 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 requires; + ASSERT_EQ(0, ioctx.pool_requires_alignment2(&requires)); + ASSERT_FALSE(requires); +} + +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() +{ + pool_name = get_temp_pool_name(); + 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 requires; + ASSERT_EQ(0, ioctx.pool_requires_alignment2(&requires)); + ASSERT_TRUE(requires); + 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(); + + pool_name = get_temp_pool_name(); + 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 requires; + ASSERT_EQ(0, ioctx.pool_requires_alignment2(&requires)); + ASSERT_FALSE(requires); +} + +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() +{ + pool_name = get_temp_pool_name(); + 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 (strcmp(GetParam(), "cache") == 0 && cache_pool_name.empty()) { + 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 requires; + ASSERT_EQ(0, ioctx.pool_requires_alignment2(&requires)); + ASSERT_FALSE(requires); +} + +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() +{ + pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_ec_pool_pp(pool_name, s_cluster)); +} + +void RadosTestECPP::TearDownTestCase() +{ + ASSERT_EQ(0, destroy_one_ec_pool_pp(pool_name, s_cluster)); +} + +void RadosTestECPP::SetUp() +{ + ASSERT_EQ(0, cluster.ioctx_create(pool_name.c_str(), ioctx)); + nspace = get_temp_pool_name(); + ioctx.set_namespace(nspace); + bool requires; + ASSERT_EQ(0, ioctx.pool_requires_alignment2(&requires)); + ASSERT_TRUE(requires); + ASSERT_EQ(0, ioctx.pool_required_alignment2(&alignment)); + ASSERT_NE(0U, alignment); +} + +void RadosTestECPP::TearDown() +{ + 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..08ccb6082 --- /dev/null +++ b/src/test/librados/tier_cxx.cc @@ -0,0 +1,8683 @@ +// -*- 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 "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" + +using namespace librados; +using std::map; +using std::ostringstream; +using std::string; + +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(); +} + +#include "common/ceph_crypto.h" +using ceph::crypto::SHA1; +#include "rgw/rgw_common.h" + +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(15); + 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 { + 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 { + // 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) { + { + 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) { + // 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) { + // 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) { + // 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) { + 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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){ + // 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]); +} + +TEST_F(LibRadosTwoPoolsPP, TryFlush) { + // 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) { + // 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) { + // 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) { + 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) { + // 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) { + // 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) { + { + 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) { + // 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) { + 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) { + 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) { + // 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) { + // 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) { + // 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) { + // 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 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) { + // 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 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(); +} + +#include "common/ceph_crypto.h" +using ceph::crypto::SHA1; +#include "rgw/rgw_common.h" +TEST_F(LibRadosTwoPoolsPP, ManifestDedupRefRead) { + // 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 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 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 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 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 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 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 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, ManifestSnapSizeMismatch) { + // 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 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 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"); + + // 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 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 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 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)); + } +} + +class LibRadosTwoPoolsECPP : public RadosTestECPP +{ +public: + LibRadosTwoPoolsECPP() {}; + ~LibRadosTwoPoolsECPP() override {}; +protected: + static void SetUpTestCase() { + pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_ec_pool_pp(pool_name, s_cluster)); + } + static void TearDownTestCase() { + ASSERT_EQ(0, destroy_one_ec_pool_pp(pool_name, s_cluster)); + } + static std::string cache_pool_name; + + void SetUp() override { + 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 { + // 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) { + { + 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + 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) { + // 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) { + // 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) { + 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) { + { + 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) { + // 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) { + 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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) { + // 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 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..79cdd5abe --- /dev/null +++ b/src/test/librados/watch_notify.cc @@ -0,0 +1,649 @@ +#include "include/rados/librados.h" +#include "include/rados/rados_types.h" +#include "test/librados/test.h" +#include "test/librados/TestCase.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) { + 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); + ASSERT_EQ(-ENOTCONN, rados_watch_check(ioctx, handle)); + 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); + ASSERT_EQ(-ENOTCONN, rados_watch_check(ioctx, handle)); + 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, ¬ify_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, ¬ify_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 = 16 * 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..0a145addd --- /dev/null +++ b/src/test/librados/watch_notify_cxx.cc @@ -0,0 +1,414 @@ +#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" + + +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) { + 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) { + 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..02a87b2c8 --- /dev/null +++ b/src/test/librados_test_stub/LibradosTestStub.cc @@ -0,0 +1,1564 @@ +// -*- 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 + +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, NULL, 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, 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) { + 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..7937e221a --- /dev/null +++ b/src/test/librados_test_stub/MockTestMemIoCtxImpl.h @@ -0,0 +1,250 @@ +// -*- 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_METHOD5(aio_operate, int(const std::string&, TestObjectOperationImpl&, + AioCompletionImpl*, SnapContext*, int)); + int do_aio_operate(const std::string& o, TestObjectOperationImpl& ops, + AioCompletionImpl* c, SnapContext* snapc, int flags) { + return TestMemIoCtxImpl::aio_operate(o, ops, c, snapc, 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..882e80820 --- /dev/null +++ b/src/test/librados_test_stub/NeoradosTestStub.cc @@ -0,0 +1,599 @@ +// -*- 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::placeholders; + +namespace neorados { +namespace detail { + +struct Client { + 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, 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..73bc8d4a8 --- /dev/null +++ b/src/test/librados_test_stub/TestIoCtxImpl.cc @@ -0,0 +1,392 @@ +// -*- 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> + +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, + 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..3c6ff590d --- /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, + 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..f829dd52c --- /dev/null +++ b/src/test/librados_test_stub/TestMemCluster.cc @@ -0,0 +1,200 @@ +// -*- 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) { + m_watch_notify.blocklist(nonce); + + std::lock_guard locker{m_lock}; + m_blocklist.insert(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..d5b1922dd --- /dev/null +++ b/src/test/librados_test_stub/TestMemCluster.h @@ -0,0 +1,125 @@ +// -*- 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 "common/RWLock.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..687abb16d --- /dev/null +++ b/src/test/librados_test_stub/TestMemIoCtxImpl.cc @@ -0,0 +1,925 @@ +// -*- 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 "common/RWLock.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..3b18026bb --- /dev/null +++ b/src/test/librados_test_stub/TestWatchNotify.cc @@ -0,0 +1,450 @@ +// -*- 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([=](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([=](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();) { + if (w_it->second.nonce == nonce) { + 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..2f931e5e0 --- /dev/null +++ b/src/test/libradosstriper/io.cc @@ -0,0 +1,406 @@ +#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 psize; + time_t pmtime; + 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", &psize, &pmtime)); + ASSERT_EQ(psize, sizeof(buf)); + ASSERT_EQ(-ENOENT, rados_striper_stat(striper, "nonexistent", &psize, &pmtime)); +} + +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 psize; + time_t pmtime; + ASSERT_EQ(0, striper.stat("Statpp", &psize, &pmtime)); + ASSERT_EQ(psize, sizeof(buf)); + ASSERT_EQ(-ENOENT, striper.stat("nonexistent", &psize, &pmtime)); +} + +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)); |