]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
neorados/cls: Client for version objclass
authorAdam C. Emerson <aemerson@redhat.com>
Sat, 21 Jan 2023 02:50:50 +0000 (21:50 -0500)
committerAdam C. Emerson <aemerson@redhat.com>
Tue, 1 Apr 2025 15:10:13 +0000 (11:10 -0400)
Signed-off-by: Adam C. Emerson <aemerson@redhat.com>
src/neorados/CMakeLists.txt
src/neorados/cls/version.h [new file with mode: 0644]
src/test/cls_version/CMakeLists.txt
src/test/cls_version/test_neocls_version.cc [new file with mode: 0644]

index 119554f67d102b69493d3644c0f214086a2851f9..cc03ce61165004c5be0c7eaf4056c1ceec3b47ae 100644 (file)
@@ -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}
-#     $<TARGET_OBJECTS:neorados_api_obj>
-#     $<TARGET_OBJECTS:neorados_objs>
-#     $<TARGET_OBJECTS:common_buffer_obj>)
-#   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
-#     $<TARGET_OBJECTS:neorados_api_obj>
-#     $<TARGET_OBJECTS:neorados_objs>)
-# 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 (file)
index 0000000..c5bc08b
--- /dev/null
@@ -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 <coroutine>
+#include <string>
+#include <utility>
+
+#include <boost/asio/async_result.hpp>
+#include <boost/system/error_code.hpp>
+
+#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<boost::asio::completion_token_for<
+          void(boost::system::error_code, obj_version)> CompletionToken>
+inline auto read(RADOS& r, Object o, IOContext ioc,
+                CompletionToken&& token)
+{
+  using namespace std::literals;
+  return exec<cls_version_read_ret>(
+    r, std::move(o), std::move(ioc),
+    "version"s, "read"s, nullptr,
+    [](cls_version_read_ret&& ret) {
+      return std::move(ret.objv);
+    }, std::forward<CompletionToken>(token));
+}
+} // namespace neorados::cls::version
index 05264017b73cd83349e87a2b54dde7570826e595..5f1d8662a718615d9fa496321584b0fed2b2dbd2 100644 (file)
@@ -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 (file)
index 0000000..2bfb66c
--- /dev/null
@@ -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 <boost/asio/post.hpp>
+#include <coroutine>
+#include <memory>
+#include <string_view>
+#include <utility>
+
+#include <boost/asio/use_awaitable.hpp>
+
+#include <boost/system/errc.hpp>
+#include <boost/system/error_code.hpp>
+
+#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<neorados::RADOS> 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);
+}