diff options
Diffstat (limited to 'src/test/librados')
43 files changed, 23657 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")); |