From c274fd3f2ea52d94c40b1dfeee862bc6202d6e7e Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Fri, 20 Jan 2023 21:50:50 -0500 Subject: [PATCH] neorados/cls: Client for version objclass Signed-off-by: Adam C. Emerson --- src/neorados/CMakeLists.txt | 26 --- src/neorados/cls/version.h | 193 ++++++++++++++++ src/test/cls_version/CMakeLists.txt | 16 ++ src/test/cls_version/test_neocls_version.cc | 236 ++++++++++++++++++++ 4 files changed, 445 insertions(+), 26 deletions(-) create mode 100644 src/neorados/cls/version.h create mode 100644 src/test/cls_version/test_neocls_version.cc diff --git a/src/neorados/CMakeLists.txt b/src/neorados/CMakeLists.txt index 119554f67d1..cc03ce61165 100644 --- a/src/neorados/CMakeLists.txt +++ b/src/neorados/CMakeLists.txt @@ -13,29 +13,3 @@ add_library(libneorados STATIC target_link_libraries(libneorados PRIVATE osdc ceph-common cls_lock_client ${FMT_LIB} ${BLKID_LIBRARIES} ${CRYPTO_LIBS} ${EXTRALIBS}) - -# if(ENABLE_SHARED) -# add_library(libneorados ${CEPH_SHARED} -# $ -# $ -# $) -# set_target_properties(libneorados PROPERTIES -# OUTPUT_NAME RADOS -# VERSION 0.0.1 -# SOVERSION 1 -# CXX_VISIBILITY_PRESET hidden -# VISIBILITY_INLINES_HIDDEN ON) -# if(NOT APPLE) -# set_property(TARGET libneorados APPEND_STRING PROPERTY -# LINK_FLAGS " -Wl,--exclude-libs,ALL") -# endif() -# else(ENABLE_SHARED) -# add_library(libneorados STATIC -# $ -# $) -# endif(ENABLE_SHARED) -# target_link_libraries(libneorados PRIVATE -# osdc ceph-common cls_lock_client -# ${BLKID_LIBRARIES} ${CRYPTO_LIBS} ${EXTRALIBS}) -# target_link_libraries(libneorados ${rados_libs}) -# install(TARGETS libneorados DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/neorados/cls/version.h b/src/neorados/cls/version.h new file mode 100644 index 00000000000..c5bc08bf660 --- /dev/null +++ b/src/neorados/cls/version.h @@ -0,0 +1,193 @@ +// -*- 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) 2023 IBM + * + * See file COPYING for license information. + * + */ + +#pragma once + +/// \file neorados/cls/version.h +/// +/// \brief NeoRADOS interface to object versioning class +/// +/// The `version` object class stores a version in the extended +/// attributes of an object to help coordinate multiple writers. This +/// version comprises a byte string and an integer. The integers are +/// comparable and can be compared with the operators in +/// `VersionCond`. The byte strings are incomparable. Two versions +/// with different strings will always conflict. + +#include +#include +#include + +#include +#include + +#include "include/neorados/RADOS.hpp" + +#include "cls/version/cls_version_ops.h" +#include "cls/version/cls_version_types.h" + +#include "neorados/cls/common.h" + +namespace neorados::cls::version { +/// \brief Set the object version +/// +/// Append a call to a write operation to set the object's version. +/// +/// \param ver Version to set +/// +/// \return The ClsWriteOp to be passed to WriteOp::exec +[[nodiscard]] inline auto set(const obj_version& ver) +{ + buffer::list in; + cls_version_set_op call; + call.objv = ver; + encode(call, in); + return ClsWriteOp{[in = std::move(in)](WriteOp& op) { + op.exec("version", "set", in); + }}; +} + +/// \brief Unconditional increment version +/// +/// Append a call to a write operation to increment the integral +/// portion of a version. +/// +/// \return The ClsWriteOp to be passed to WriteOp::exec +[[nodiscard]] inline auto inc() +{ + buffer::list in; + cls_version_inc_op call; + encode(call, in); + return ClsWriteOp{[in = std::move(in)](WriteOp& op) { + op.exec("version", "inc", in); + }}; +} + +/// \brief Conditionally increment version +/// +/// Append a call to a write operation to increment the object's +/// version if condition is met. If the condition is not met, the +/// operation fails with `std::errc::resource_unavailable_try_again`. +/// +/// \param ver Version to compare stored object version against +/// \param cond Comparison operator +/// +/// \return The ClsWriteOp to be passed to WriteOp::exec +[[nodiscard]] inline auto inc(const obj_version& objv, const VersionCond cond) +{ + buffer::list in; + cls_version_inc_op call; + call.objv = objv; + + obj_version_cond c; + c.cond = cond; + c.ver = objv; + + call.conds.push_back(c); + + encode(call, in); + return ClsWriteOp{[in = std::move(in)](WriteOp& op) { + op.exec("version", "inc_conds", in); + }}; +} + +/// \brief Assert condition on stored version +/// +/// Append a call to an operation that verifies the stored version has +/// the specified relationship to the supplied version. If the +/// condition is not met, the operation fails with +/// `std::errc::operation_canceled`. +/// +/// \param ver Version to compare stored object version against +/// \param cond Comparison operator +/// +/// \return The ClsOp to be passed to {Read,Write}Op::exec +[[nodiscard]] inline auto check(const obj_version& ver, const VersionCond cond) +{ + buffer::list in; + cls_version_check_op call; + call.objv = ver; + + obj_version_cond c; + c.cond = cond; + c.ver = ver; + + call.conds.push_back(c); + + encode(call, in); + return ClsOp{[in = std::move(in)](Op& op) { + op.exec("version", "check_conds", in); + }}; +} + +/// \brief Read the stored object version +/// +/// Append a call to a read operation that reads the stored version. +/// +/// \param objv Location to store the version +/// +/// \return The ClsReadOp to be passed to ReadOp::exec +[[nodiscard]] inline auto read(obj_version* const objv) +{ + using boost::system::error_code; + return ClsReadOp{[objv](Op& op) { + namespace sys = boost::system; + op.exec("version", "read", {}, + [objv](error_code ec, + const buffer::list& bl) { + cls_version_read_ret ret; + if (!ec) { + auto iter = bl.cbegin(); + try { + decode(ret, iter); + } catch (const sys::system_error& e) { + // This works by accident in the paleorados version, + // since they just don't report decode errors. + if (e.code() == buffer::errc::end_of_buffer) { + if (objv) { + objv->clear(); + } + } else { + throw; + } + } + if (objv) + *objv = std::move(ret.objv); + } + }); + }}; +} + +/// \brief Read the stored object version +/// +/// Execute an asynchronous operation that reads the stored version. +/// +/// \param r RADOS handle +/// \param o Object to query +/// \param ioc IOContext determining the object location +/// \param token Boost.Asio CompletionToken +/// +/// \return The object version in a way appropriate to the completion +/// token. See Boost.Asio documentation. +template CompletionToken> +inline auto read(RADOS& r, Object o, IOContext ioc, + CompletionToken&& token) +{ + using namespace std::literals; + return exec( + r, std::move(o), std::move(ioc), + "version"s, "read"s, nullptr, + [](cls_version_read_ret&& ret) { + return std::move(ret.objv); + }, std::forward(token)); +} +} // namespace neorados::cls::version diff --git a/src/test/cls_version/CMakeLists.txt b/src/test/cls_version/CMakeLists.txt index 05264017b73..5f1d8662a71 100644 --- a/src/test/cls_version/CMakeLists.txt +++ b/src/test/cls_version/CMakeLists.txt @@ -14,3 +14,19 @@ target_link_libraries(ceph_test_cls_version radostest-cxx ) + +add_executable(ceph_test_neocls_version + test_neocls_version.cc + ) +target_link_libraries(ceph_test_neocls_version + libneorados + ${BLKID_LIBRARIES} + ${CMAKE_DL_LIBS} + ${CRYPTO_LIBS} + ${EXTRALIBS} + neoradostest-support + ${UNITTEST_LIBS} + ) +install(TARGETS + ceph_test_neocls_version + DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/test/cls_version/test_neocls_version.cc b/src/test/cls_version/test_neocls_version.cc new file mode 100644 index 00000000000..2bfb66c231c --- /dev/null +++ b/src/test/cls_version/test_neocls_version.cc @@ -0,0 +1,236 @@ +// -*- 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) 2023 IBM + * + * See file COPYING for license information. + * + */ + +#include "neorados/cls/version.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "include/neorados/RADOS.hpp" + +#include "cls/version/cls_version_types.h" + +#include "test/neorados/common_tests.h" + +#include "gtest/gtest.h" + +namespace asio = boost::asio; +namespace version = neorados::cls::version; +using neorados::ReadOp; +using neorados::WriteOp; + +using boost::system::error_code; +using boost::system::errc::operation_canceled; + +CORO_TEST_F(neocls_version, test_version_inc_read, NeoRadosTest) +{ + std::string_view oid = "obj"; + co_await create_obj(oid); + + auto ver = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + EXPECT_EQ(0u, ver.ver); + EXPECT_EQ(0u, ver.tag.size()); + + // Increment version + co_await execute(oid, WriteOp{}.exec(version::inc())); + + ver = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + EXPECT_GT(ver.ver, 0u); + EXPECT_NE(0u, ver.tag.size()); + + co_await execute(oid, WriteOp{}.exec(version::inc())); + + auto ver2 = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + + EXPECT_GT(ver2.ver, ver.ver); + EXPECT_EQ(0u, ver2.tag.compare(ver.tag)); + + obj_version ver3; + co_await execute(oid, ReadOp{}.exec(version::read(&ver3))); + EXPECT_EQ(ver2.ver, ver3.ver); + EXPECT_EQ(1u, ver2.compare(&ver3)); + co_return; +} + +CORO_TEST_F(neocls_version, test_version_set, NeoRadosTest) +{ + std::string_view oid = "obj"; + co_await create_obj(oid); + + auto ver = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + EXPECT_EQ(0u, ver.ver); + EXPECT_EQ(0u, ver.tag.size()); + + ver.ver = 123; + ver.tag = "foo"; + + // Set version + co_await execute(oid, WriteOp{}.exec(version::set(ver))); + + auto ver2 = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + + EXPECT_EQ(ver2.ver, ver.ver); + EXPECT_EQ(0, ver2.tag.compare(ver.tag)); + co_return; +} + +CORO_TEST_F(neocls_version, test_version_inc_cond, NeoRadosTest) +{ + std::string_view oid = "obj"; + co_await create_obj(oid); + + auto ver = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + + EXPECT_EQ(0u, ver.ver); + EXPECT_EQ(0u, ver.tag.size()); + + // Increment version + co_await execute(oid, WriteOp{}.exec(version::inc())); + ver = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + EXPECT_GT(ver.ver, 0u); + EXPECT_NE(0, ver.tag.size()); + + auto cond_ver = ver; + + co_await execute(oid, WriteOp{}.exec(version::inc())); + + auto ver2 = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + EXPECT_GT(ver2.ver, ver.ver); + EXPECT_EQ(0u, ver2.tag.compare(ver.tag)); + + // Now check various condition tests + co_await execute(oid, WriteOp{}.exec(version::inc(cond_ver, VER_COND_NONE))); + + ver2 = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + EXPECT_GT(ver2.ver, ver.ver); + EXPECT_EQ(0u, ver2.tag.compare(ver.tag)); + + // A bunch of conditions that should fail + co_await expect_error_code( + execute(oid, WriteOp{}.exec(version::inc(cond_ver, VER_COND_EQ))), + operation_canceled); + + co_await expect_error_code( + execute(oid, WriteOp{}.exec(version::inc(cond_ver, VER_COND_LT))), + operation_canceled); + + co_await expect_error_code( + execute(oid, WriteOp{}.exec(version::inc(cond_ver, VER_COND_LE))), + operation_canceled); + + co_await expect_error_code( + execute(oid, WriteOp{}.exec(version::inc(cond_ver, VER_COND_TAG_NE))), + operation_canceled); + + ver2 = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + EXPECT_GT(ver2.ver, ver.ver); + EXPECT_EQ(0u, ver2.tag.compare(ver.tag)); + + /* a bunch of conditions that should succeed */ + co_await execute(oid, WriteOp{}.exec(version::inc(ver2, VER_COND_EQ))); + co_await execute(oid, WriteOp{}.exec(version::inc(cond_ver, VER_COND_GT))); + co_await execute(oid, WriteOp{}.exec(version::inc(cond_ver, VER_COND_GE))); + + co_await execute(oid, WriteOp{} + .exec(version::inc(cond_ver, VER_COND_TAG_EQ))); +} + +CORO_TEST_F(neocls_version, test_version_inc_check, NeoRadosTest) +{ + std::string_view oid = "obj"; + co_await create_obj(oid); + + auto ver = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + EXPECT_EQ(0u, ver.ver); + EXPECT_EQ(0u, ver.tag.size()); + + // Increment version + co_await execute(oid, WriteOp{}.exec(version::inc())); + + ver = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + EXPECT_GT(ver.ver, 0u); + EXPECT_NE(0u, ver.tag.size()); + + obj_version cond_ver = ver; + + // a bunch of conditions that should succeed + co_await execute(oid, ReadOp{}.exec(version::check(cond_ver, VER_COND_EQ))); + + co_await execute(oid, ReadOp{}.exec(version::check(cond_ver, VER_COND_GE))); + + co_await execute(oid, ReadOp{}.exec(version::check(cond_ver, VER_COND_LE))); + + co_await execute(oid, ReadOp{} + .exec(version::check(cond_ver, VER_COND_TAG_EQ))); + + co_await execute(oid, WriteOp{}.exec(version::inc())); + + auto ver2 = co_await version::read(rados(), oid, pool(), asio::use_awaitable); + EXPECT_GT(ver2.ver, ver.ver); + EXPECT_EQ(0, ver2.tag.compare(ver.tag)); + + // A bunch of conditions that should fail + co_await expect_error_code( + execute(oid, ReadOp{}.exec(version::check(ver, VER_COND_LT))), + operation_canceled); + + co_await expect_error_code( + execute(oid, ReadOp{}.exec(version::check(ver, VER_COND_LE))), + operation_canceled); + + co_await expect_error_code( + execute(oid, ReadOp{}.exec(version::check(ver, VER_COND_TAG_NE))), + operation_canceled); +} + +TEST(neocls_version_bare, lambdata) +{ + asio::io_context c; + + std::string_view oid = "obj"; + + obj_version iver{123, "foo"}; + obj_version ever; + + std::optional rados; + neorados::IOContext pool; + neorados::RADOS::Builder{}.build(c, [&](error_code ec, neorados::RADOS r_) { + ASSERT_FALSE(ec); + rados = std::move(r_); + create_pool(*rados, get_temp_pool_name(), + [&](error_code ec, int64_t poolid) { + ASSERT_FALSE(ec); + pool.set_pool(poolid); + neorados::WriteOp op; + op.create(true); + op.exec(version::set(iver)); + rados->execute(oid, pool, std::move(op), [&](error_code ec) { + ASSERT_FALSE(ec); + version::read(*rados, oid, pool, + [&](error_code ec, obj_version over) { + ASSERT_FALSE(ec); + ASSERT_EQ(iver, over); + ever = over; + }); + }); + }); + }); + c.run(); + ASSERT_EQ(iver, ever); +} -- 2.39.5