]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
test/cls: add ceph_test_cls_user
authorCasey Bodley <cbodley@redhat.com>
Sun, 19 Nov 2023 16:32:37 +0000 (11:32 -0500)
committerCasey Bodley <cbodley@redhat.com>
Fri, 12 Apr 2024 19:34:27 +0000 (15:34 -0400)
Signed-off-by: Casey Bodley <cbodley@redhat.com>
(cherry picked from commit a49757e5c3e7580399802345babd2dde4e3e661b)

qa/suites/rgw/verify/tasks/cls.yaml
qa/workunits/cls/test_cls_user.sh [new file with mode: 0755]
src/test/CMakeLists.txt
src/test/cls_user/CMakeLists.txt [new file with mode: 0644]
src/test/cls_user/test_cls_user.cc [new file with mode: 0644]

index 568395a0dd0ef95660affce85eb4b797c2cd23ea..8034715353f70b1c834f819deea3a80dcc085c77 100644 (file)
@@ -10,6 +10,7 @@ tasks:
         - cls/test_cls_rgw_stats.sh
         - cls/test_cls_cmpomap.sh
         - cls/test_cls_2pc_queue.sh
+        - cls/test_cls_user.sh
         - rgw/test_rgw_gc_log.sh
         - rgw/test_rgw_obj.sh
         - rgw/test_librgw_file.sh
diff --git a/qa/workunits/cls/test_cls_user.sh b/qa/workunits/cls/test_cls_user.sh
new file mode 100755 (executable)
index 0000000..9df1f4b
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh -e
+
+ceph_test_cls_user
+
+exit 0
index 8f57b2db9b6ca0259b67fa4ccd7ee05af5355285..a0c8fcfe823cca7fddfebbb4c53c65775aa6a141 100644 (file)
@@ -49,6 +49,7 @@ if(NOT WIN32)
   if(WITH_RADOSGW)
     add_subdirectory(cls_rgw)
     add_subdirectory(cls_rgw_gc)
+    add_subdirectory(cls_user)
   endif(WITH_RADOSGW)
   add_subdirectory(cls_refcount)
   add_subdirectory(cls_version)
diff --git a/src/test/cls_user/CMakeLists.txt b/src/test/cls_user/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9796205
--- /dev/null
@@ -0,0 +1,5 @@
+add_executable(ceph_test_cls_user test_cls_user.cc
+  $<TARGET_OBJECTS:unit-main>)
+target_link_libraries(ceph_test_cls_user cls_user_client global
+  librados ${UNITTEST_LIBS} radostest-cxx)
+install(TARGETS ceph_test_cls_user DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/test/cls_user/test_cls_user.cc b/src/test/cls_user/test_cls_user.cc
new file mode 100644 (file)
index 0000000..c37f7a8
--- /dev/null
@@ -0,0 +1,211 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright contributors to the Ceph project
+ *
+ * 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 "cls/user/cls_user_client.h"
+#include "test/librados/test_cxx.h"
+#include "gtest/gtest.h"
+
+#include <optional>
+#include <system_error>
+#include "include/expected.hpp"
+
+// create/destroy a pool that's shared by all tests in the process
+struct RadosEnv : public ::testing::Environment {
+  static std::optional<std::string> pool_name;
+ public:
+  static librados::Rados rados;
+  static librados::IoCtx ioctx;
+
+  void SetUp() override {
+    // create pool
+    std::string name = get_temp_pool_name();
+    ASSERT_EQ("", create_one_pool_pp(name, rados));
+    pool_name = name;
+    ASSERT_EQ(rados.ioctx_create(name.c_str(), ioctx), 0);
+  }
+  void TearDown() override {
+    ioctx.close();
+    if (pool_name) {
+      ASSERT_EQ(destroy_one_pool_pp(*pool_name, rados), 0);
+    }
+  }
+};
+std::optional<std::string> RadosEnv::pool_name;
+librados::Rados RadosEnv::rados;
+librados::IoCtx RadosEnv::ioctx;
+
+auto *const rados_env = ::testing::AddGlobalTestEnvironment(new RadosEnv);
+
+// test fixture with helper functions
+class ClsAccount : public ::testing::Test {
+ protected:
+  librados::IoCtx& ioctx = RadosEnv::ioctx;
+
+  int add(const std::string& oid, const cls_user_account_resource& entry,
+          bool exclusive, uint32_t limit)
+  {
+    librados::ObjectWriteOperation op;
+    cls_user_account_resource_add(op, entry, exclusive, limit);
+    return ioctx.operate(oid, &op);
+  }
+
+  auto get(const std::string& oid, std::string_view name)
+      -> tl::expected<cls_user_account_resource, int>
+  {
+    librados::ObjectReadOperation op;
+    cls_user_account_resource resource;
+    int r2 = 0;
+    cls_user_account_resource_get(op, name, resource, &r2);
+
+    int r1 = ioctx.operate(oid, &op, nullptr);
+    if (r1 < 0) return tl::unexpected(r1);
+    if (r2 < 0) return tl::unexpected(r2);
+    return resource;
+  }
+
+  int rm(const std::string& oid, std::string_view name)
+  {
+    librados::ObjectWriteOperation op;
+    cls_user_account_resource_rm(op, name);
+    return ioctx.operate(oid, &op);
+  }
+
+  int list(const std::string& oid, std::string_view marker,
+           std::string_view path_prefix, uint32_t max_entries,
+           std::vector<cls_user_account_resource>& entries, bool& truncated,
+           std::string& next_marker, int& ret)
+  {
+    librados::ObjectReadOperation op;
+    cls_user_account_resource_list(op, marker, path_prefix, max_entries,
+                                   entries, &truncated, &next_marker, &ret);
+    return ioctx.operate(oid, &op, nullptr);
+  }
+
+  auto list_all(const std::string& oid,
+                std::string_view path_prefix = "",
+                uint32_t max_chunk = 1000)
+    -> std::vector<cls_user_account_resource>
+  {
+    std::vector<cls_user_account_resource> all_entries;
+    std::string marker;
+    bool truncated = true;
+
+    while (truncated) {
+      std::vector<cls_user_account_resource> entries;
+      std::string next_marker;
+      int r2 = 0;
+      int r1 = list(oid, marker, path_prefix, max_chunk,
+                    entries, truncated, next_marker, r2);
+      if (r1 < 0) throw std::system_error(r1, std::system_category());
+      if (r2 < 0) throw std::system_error(r2, std::system_category());
+      marker = std::move(next_marker);
+      std::move(entries.begin(), entries.end(),
+                std::back_inserter(all_entries));
+    }
+    return all_entries;
+  }
+};
+
+template <typename ...Args>
+std::vector<cls_user_account_resource> make_list(Args&& ...args)
+{
+  return {std::forward<Args>(args)...};
+}
+
+bool operator==(const cls_user_account_resource& lhs,
+                const cls_user_account_resource& rhs)
+{
+  if (lhs.name != rhs.name) {
+    return false;
+  }
+  return lhs.path == rhs.path;
+  // ignore metadata
+}
+std::ostream& operator<<(std::ostream& out, const cls_user_account_resource& r)
+{
+  return out << r.path << r.name;
+}
+
+TEST_F(ClsAccount, add)
+{
+  const std::string oid = __PRETTY_FUNCTION__;
+  const auto u1 = cls_user_account_resource{.name = "user1"};
+  const auto u2 = cls_user_account_resource{.name = "user2"};
+  const auto u3 = cls_user_account_resource{.name = "USER2"};
+  EXPECT_EQ(-EUSERS, add(oid, u1, true, 0));
+  EXPECT_EQ(0, add(oid, u1, true, 1));
+  EXPECT_EQ(-EUSERS, add(oid, u2, true, 1));
+  EXPECT_EQ(-EEXIST, add(oid, u1, true, 1));
+  EXPECT_EQ(0, add(oid, u1, false, 1)); // allow overwrite at limit
+  EXPECT_EQ(0, add(oid, u2, true, 2));
+  EXPECT_EQ(-EEXIST, add(oid, u3, true, 2)); // case-insensitive match
+}
+
+TEST_F(ClsAccount, get)
+{
+  const std::string oid = __PRETTY_FUNCTION__;
+  const auto u1 = cls_user_account_resource{.name = "user1", .path = "A"};
+  const auto u2 = cls_user_account_resource{.name = "USER1"};
+  EXPECT_EQ(tl::unexpected(-ENOENT), get(oid, u1.name));
+  EXPECT_EQ(-EUSERS, add(oid, u1, true, 0));
+  EXPECT_EQ(tl::unexpected(-ENOENT), get(oid, u1.name));
+  EXPECT_EQ(0, add(oid, u1, true, 1));
+  EXPECT_EQ(u1, get(oid, u1.name));
+  EXPECT_EQ(0, add(oid, u2, false, 1)); // overwrite with different case
+  EXPECT_EQ(u2, get(oid, u1.name)); // accessible by the original name
+}
+
+TEST_F(ClsAccount, rm)
+{
+  const std::string oid = __PRETTY_FUNCTION__;
+  const auto u1 = cls_user_account_resource{.name = "user1"};
+  const auto u2 = cls_user_account_resource{.name = "USER1"};
+  EXPECT_EQ(-ENOENT, rm(oid, u1.name));
+  ASSERT_EQ(0, add(oid, u1, true, 1));
+  ASSERT_EQ(0, rm(oid, u1.name));
+  EXPECT_EQ(-ENOENT, rm(oid, u1.name));
+  ASSERT_EQ(0, add(oid, u1, true, 1));
+  ASSERT_EQ(0, rm(oid, u2.name)); // case-insensitive match
+}
+
+TEST_F(ClsAccount, list)
+{
+  const std::string oid = __PRETTY_FUNCTION__;
+  const auto u1 = cls_user_account_resource{.name = "user1", .path = ""};
+  const auto u2 = cls_user_account_resource{.name = "User2", .path = "A"};
+  const auto u3 = cls_user_account_resource{.name = "user3", .path = "AA"};
+  const auto u4 = cls_user_account_resource{.name = "User4", .path = ""};
+  const auto u5 = cls_user_account_resource{.name = "USER1", .path = "z"};
+  constexpr uint32_t max_users = 1024;
+
+  ASSERT_EQ(0, ioctx.create(oid, true));
+  ASSERT_EQ(make_list(), list_all(oid));
+  ASSERT_EQ(0, add(oid, u1, true, max_users));
+  EXPECT_EQ(make_list(u1), list_all(oid));
+  ASSERT_EQ(0, add(oid, u2, true, max_users));
+  ASSERT_EQ(0, add(oid, u3, true, max_users));
+  ASSERT_EQ(0, add(oid, u4, true, max_users));
+  EXPECT_EQ(make_list(u1, u2, u3, u4), list_all(oid, ""));
+  EXPECT_EQ(make_list(u1, u2, u3, u4), list_all(oid, "", 1)); // paginated
+  EXPECT_EQ(make_list(u2, u3), list_all(oid, "A"));
+  EXPECT_EQ(make_list(u2, u3), list_all(oid, "A", 1)); // paginated
+  EXPECT_EQ(make_list(u3), list_all(oid, "AA"));
+  EXPECT_EQ(make_list(u3), list_all(oid, "AA", 1)); // paginated
+  EXPECT_EQ(make_list(), list_all(oid, "AAu")); // don't match AAuser3
+  ASSERT_EQ(0, rm(oid, u2.name));
+  EXPECT_EQ(make_list(u1, u3, u4), list_all(oid, ""));
+  EXPECT_EQ(make_list(u1, u3, u4), list_all(oid, "", 1)); // paginated
+  ASSERT_EQ(0, add(oid, u5, false, max_users)); // overwrite u1
+  EXPECT_EQ(make_list(u5, u3, u4), list_all(oid, ""));
+}