]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
rgw/rados: implement account metadata operations
authorCasey Bodley <cbodley@redhat.com>
Mon, 27 Nov 2023 23:12:22 +0000 (18:12 -0500)
committerCasey Bodley <cbodley@redhat.com>
Wed, 10 Apr 2024 16:53:05 +0000 (12:53 -0400)
Signed-off-by: Casey Bodley <cbodley@redhat.com>
src/rgw/CMakeLists.txt
src/rgw/driver/rados/account.cc [new file with mode: 0644]
src/rgw/driver/rados/account.h [new file with mode: 0644]
src/rgw/driver/rados/rgw_sal_rados.cc
src/rgw/driver/rados/rgw_user.h

index ed21cfba9c119169176dec52436c60a986d402c4..ef4d24b0c3bc741a309acce84f5080e37fa7233d 100644 (file)
@@ -150,6 +150,7 @@ set(librgw_common_srcs
   rgw_tracer.cc
   rgw_lua_background.cc
   rgw_data_access.cc
+  driver/rados/account.cc
   driver/rados/cls_fifo_legacy.cc
   driver/rados/rgw_bucket.cc
   driver/rados/rgw_bucket_sync.cc
diff --git a/src/rgw/driver/rados/account.cc b/src/rgw/driver/rados/account.cc
new file mode 100644 (file)
index 0000000..acc4dee
--- /dev/null
@@ -0,0 +1,391 @@
+// -*- 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 "account.h"
+
+#include <boost/algorithm/string.hpp>
+#include "common/errno.h"
+#include "rgw_account.h"
+#include "rgw_common.h"
+#include "rgw_obj_types.h"
+#include "rgw_string.h"
+#include "rgw_tools.h"
+#include "rgw_user.h"
+#include "rgw_zone.h"
+#include "services/svc_sys_obj.h"
+
+namespace rgwrados::account {
+
+static constexpr std::string_view buckets_oid_prefix = "buckets.";
+static const std::string account_oid_prefix = "account.";
+static constexpr std::string_view name_oid_prefix = "name.";
+
+// metadata keys/objects
+static std::string get_buckets_key(std::string_view account_id) {
+  return string_cat_reserve(buckets_oid_prefix, account_id);
+}
+rgw_raw_obj get_buckets_obj(const RGWZoneParams& zone,
+                            std::string_view account_id) {
+  return {zone.account_pool, get_buckets_key(account_id)};
+}
+
+static std::string get_account_key(std::string_view account_id) {
+  return string_cat_reserve(account_oid_prefix, account_id);
+}
+static rgw_raw_obj get_account_obj(const RGWZoneParams& zone,
+                                   std::string_view account_id) {
+  return {zone.account_pool, get_account_key(account_id)};
+}
+
+static std::string get_name_key(std::string_view tenant,
+                                std::string_view name) {
+  return string_cat_reserve(name_oid_prefix, tenant, "$", name);
+}
+static rgw_raw_obj get_name_obj(const RGWZoneParams& zone,
+                                std::string_view tenant,
+                                std::string_view name) {
+  return {zone.account_pool, get_name_key(tenant, name)};
+}
+
+// store in lower case for case-insensitive matching
+static std::string get_email_key(std::string_view email) {
+  auto lower = std::string{email};
+  boost::to_lower(lower);
+  return lower;
+}
+// note that account email oids conflict with user email oids. this ensures
+// that all emails are globally unique. we rely on rgw::account::validate_id()
+// to distinguish between user and account ids
+static rgw_raw_obj get_email_obj(const RGWZoneParams& zone,
+                                 std::string_view email) {
+  return {zone.user_email_pool, get_email_key(email)};
+}
+
+
+struct RedirectObj {
+  rgw_raw_obj obj;
+  RGWUID data;
+  RGWObjVersionTracker objv;
+};
+
+static int read_redirect(const DoutPrefixProvider* dpp,
+                         optional_yield y,
+                         RGWSI_SysObj& sysobj,
+                         RedirectObj& redirect)
+{
+  bufferlist bl;
+  int r = rgw_get_system_obj(&sysobj, redirect.obj.pool, redirect.obj.oid,
+                             bl, &redirect.objv, nullptr, y, dpp);
+  if (r < 0) {
+    ldpp_dout(dpp, 20) << "failed to read " << redirect.obj.oid
+        << " with: " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  try {
+    auto p = bl.cbegin();
+    decode(redirect.data, p);
+  } catch (const buffer::error& e) {
+    ldpp_dout(dpp, 0) << "ERROR: failed to decode account redirect: "
+        << e.what() << dendl;
+    return -EIO;
+  }
+  return 0;
+}
+
+static int write_redirect(const DoutPrefixProvider* dpp,
+                          optional_yield y,
+                          RGWSI_SysObj& sysobj,
+                          RedirectObj& redirect)
+{
+  bufferlist bl;
+  encode(redirect.data, bl);
+
+  constexpr bool exclusive = true;
+  return rgw_put_system_obj(dpp, &sysobj, redirect.obj.pool,
+                            redirect.obj.oid, bl, exclusive,
+                            &redirect.objv, ceph::real_time{}, y);
+}
+
+
+int read(const DoutPrefixProvider* dpp,
+         optional_yield y,
+         RGWSI_SysObj& sysobj,
+         const RGWZoneParams& zone,
+         std::string_view account_id,
+         RGWAccountInfo& info,
+         std::map<std::string, ceph::buffer::list>& attrs,
+         ceph::real_time& mtime,
+         RGWObjVersionTracker& objv)
+{
+  const rgw_raw_obj obj = get_account_obj(zone, account_id);
+
+  bufferlist bl;
+  int r = rgw_get_system_obj(&sysobj, obj.pool, obj.oid, bl,
+                             &objv, &mtime, y, dpp, &attrs);
+  if (r < 0) {
+    ldpp_dout(dpp, 20) << "account lookup with id=" << account_id
+        << " failed: " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  try {
+    auto p = bl.cbegin();
+    decode(info, p);
+  } catch (const buffer::error& e) {
+    ldpp_dout(dpp, 0) << "ERROR: failed to decode account info: "
+        << e.what() << dendl;
+    return -EIO;
+  }
+  if (info.id != account_id) {
+    ldpp_dout(dpp, 0) << "ERROR: read account id mismatch "
+        << info.id << " != " << account_id << dendl;
+    return -EIO;
+  }
+  return 0;
+}
+
+int read_by_name(const DoutPrefixProvider* dpp,
+                 optional_yield y,
+                 RGWSI_SysObj& sysobj,
+                 const RGWZoneParams& zone,
+                 std::string_view tenant,
+                 std::string_view name,
+                 RGWAccountInfo& info,
+                 std::map<std::string, ceph::buffer::list>& attrs,
+                 RGWObjVersionTracker& objv)
+{
+  auto redirect = RedirectObj{.obj = get_name_obj(zone, tenant, name)};
+  int r = read_redirect(dpp, y, sysobj, redirect);
+  if (r < 0) {
+    return r;
+  }
+  ceph::real_time mtime; // ignored
+  return read(dpp, y, sysobj, zone, redirect.data.id, info, attrs, mtime, objv);
+}
+
+int read_by_email(const DoutPrefixProvider* dpp,
+                  optional_yield y,
+                  RGWSI_SysObj& sysobj,
+                  const RGWZoneParams& zone,
+                  std::string_view email,
+                  RGWAccountInfo& info,
+                  std::map<std::string, ceph::buffer::list>& attrs,
+                  RGWObjVersionTracker& objv)
+{
+  auto redirect = RedirectObj{.obj = get_email_obj(zone, email)};
+  int r = read_redirect(dpp, y, sysobj, redirect);
+  if (r < 0) {
+    return r;
+  }
+  if (!rgw::account::validate_id(redirect.data.id)) {
+    // this index is used for a user, not an account
+    return -ENOENT;
+  }
+  ceph::real_time mtime; // ignored
+  return read(dpp, y, sysobj, zone, redirect.data.id, info, attrs, mtime, objv);
+}
+
+
+int write(const DoutPrefixProvider* dpp,
+          optional_yield y,
+          RGWSI_SysObj& sysobj,
+          const RGWZoneParams& zone,
+          const RGWAccountInfo& info,
+          const RGWAccountInfo* old_info,
+          const std::map<std::string, ceph::buffer::list>& attrs,
+          ceph::real_time mtime,
+          bool exclusive,
+          RGWObjVersionTracker& objv)
+{
+  const rgw_raw_obj obj = get_account_obj(zone, info.id);
+
+  const bool same_name = old_info
+      && old_info->tenant == info.tenant
+      && old_info->name == info.name;
+  const bool same_email = old_info
+      && boost::iequals(old_info->email, info.email);
+
+  std::optional<RedirectObj> remove_name;
+  std::optional<RedirectObj> remove_email;
+  if (old_info) {
+    if (old_info->id != info.id) {
+      ldpp_dout(dpp, 1) << "ERROR: can't modify account id" << dendl;
+      return -EINVAL;
+    }
+    if (!same_name && !old_info->name.empty()) {
+      // read old account name object
+      RedirectObj redirect;
+      redirect.obj = get_name_obj(zone, old_info->tenant, old_info->name);
+      int r = read_redirect(dpp, y, sysobj, redirect);
+      if (r == -ENOENT) {
+        // leave remove_name empty
+      } else if (r < 0) {
+        return r;
+      } else if (redirect.data.id == info.id) {
+        remove_name = std::move(redirect);
+      }
+    }
+    if (!same_email && !old_info->email.empty()) {
+      // read old account email object
+      RedirectObj redirect;
+      redirect.obj = get_email_obj(zone, old_info->email);
+      int r = read_redirect(dpp, y, sysobj, redirect);
+      if (r == -ENOENT) {
+        // leave remove_email empty
+      } else if (r < 0) {
+        return r;
+      } else if (redirect.data.id == info.id) {
+        remove_email = std::move(redirect);
+      }
+    }
+  } // old_info
+
+  if (!same_name && !info.name.empty()) {
+    // read new account name object
+    RedirectObj redirect;
+    redirect.obj = get_name_obj(zone, info.tenant, info.name);
+    int r = read_redirect(dpp, y, sysobj, redirect);
+    if (r == -ENOENT) {
+      // write the new name object below
+    } else if (r == 0) {
+      ldpp_dout(dpp, 1) << "ERROR: account name obj " << redirect.obj
+          << " already taken for account id " << redirect.data.id << dendl;
+      return -EEXIST;
+    } else if (r < 0) {
+      return r;
+    }
+  }
+
+  if (!same_email && !info.email.empty()) {
+    // read new account email object
+    RedirectObj redirect;
+    redirect.obj = get_email_obj(zone, info.email);
+    int r = read_redirect(dpp, y, sysobj, redirect);
+    if (r == -ENOENT) {
+      // write the new email object below
+    } else if (r == 0) {
+      ldpp_dout(dpp, 1) << "ERROR: account email obj " << redirect.obj
+          << " already taken for " << redirect.data.id << dendl;
+      return -EEXIST;
+    } else if (r < 0) {
+      return r;
+    }
+  }
+
+  // encode/write the account info
+  {
+    bufferlist bl;
+    encode(info, bl);
+
+    const rgw_raw_obj obj = get_account_obj(zone, info.id);
+    int r = rgw_put_system_obj(dpp, &sysobj, obj.pool, obj.oid, bl,
+                               exclusive, &objv, mtime, y, &attrs);
+    if (r < 0) {
+      ldpp_dout(dpp, 1) << "ERROR: failed to write account obj " << obj
+          << " with: " << cpp_strerror(r) << dendl;
+      return r;
+    }
+  }
+
+  if (remove_name) {
+    // remove the old name object, ignoring errors
+    auto& redirect = *remove_name;
+    int r = rgw_delete_system_obj(dpp, &sysobj, redirect.obj.pool,
+                                  redirect.obj.oid, &redirect.objv, y);
+    if (r < 0) {
+      ldpp_dout(dpp, 20) << "WARNING: failed to remove old name obj "
+          << redirect.obj.oid << ": " << cpp_strerror(r) << dendl;
+    } // not fatal
+  }
+  if (!same_name && !info.name.empty()) {
+    // write the new name object
+    RedirectObj redirect;
+    redirect.obj = get_name_obj(zone, info.tenant, info.name);
+    redirect.data.id = info.id;
+    redirect.objv.generate_new_write_ver(dpp->get_cct());
+
+    int r = write_redirect(dpp, y, sysobj, redirect);
+    if (r < 0) {
+      ldpp_dout(dpp, 20) << "WARNING: failed to write name obj "
+          << redirect.obj << " with: " << cpp_strerror(r) << dendl;
+    } // not fatal
+  }
+
+  if (remove_email) {
+    // remove the old email object, ignoring errors
+    auto& redirect = *remove_email;
+    int r = rgw_delete_system_obj(dpp, &sysobj, redirect.obj.pool,
+                                  redirect.obj.oid, &redirect.objv, y);
+    if (r < 0) {
+      ldpp_dout(dpp, 20) << "WARNING: failed to remove old email obj "
+          << redirect.obj.oid << ": " << cpp_strerror(r) << dendl;
+    } // not fatal
+  }
+  if (!same_email && !info.email.empty()) {
+    // write the new email object
+    RedirectObj redirect;
+    redirect.obj = get_email_obj(zone, info.email);
+    redirect.data.id = info.id;
+    redirect.objv.generate_new_write_ver(dpp->get_cct());
+
+    int r = write_redirect(dpp, y, sysobj, redirect);
+    if (r < 0) {
+      ldpp_dout(dpp, 20) << "WARNING: failed to write email obj "
+          << redirect.obj << " with: " << cpp_strerror(r) << dendl;
+    } // not fatal
+  }
+
+  return 0;
+}
+
+int remove(const DoutPrefixProvider* dpp,
+           optional_yield y,
+           RGWSI_SysObj& sysobj,
+           const RGWZoneParams& zone,
+           const RGWAccountInfo& info,
+           RGWObjVersionTracker& objv)
+{
+  const rgw_raw_obj obj = get_account_obj(zone, info.id);
+  int r = rgw_delete_system_obj(dpp, &sysobj, obj.pool, obj.oid, &objv, y);
+  if (r < 0) {
+      ldpp_dout(dpp, 1) << "ERROR: failed to remove account obj "
+          << obj << " with: " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  if (!info.name.empty()) {
+    // remove the name object
+    const rgw_raw_obj obj = get_name_obj(zone, info.tenant, info.name);
+    r = rgw_delete_system_obj(dpp, &sysobj, obj.pool, obj.oid, nullptr, y);
+    if (r < 0) {
+      ldpp_dout(dpp, 20) << "WARNING: failed to remove name obj "
+        << obj << " with: " << cpp_strerror(r) << dendl;
+    } // not fatal
+  }
+  if (!info.email.empty()) {
+    // remove the email object
+    const rgw_raw_obj obj = get_email_obj(zone, info.email);
+    r = rgw_delete_system_obj(dpp, &sysobj, obj.pool, obj.oid, nullptr, y);
+    if (r < 0) {
+      ldpp_dout(dpp, 20) << "WARNING: failed to remove email obj "
+        << obj << " with: " << cpp_strerror(r) << dendl;
+    } // not fatal
+  }
+
+  return 0;
+}
+
+} // namespace rgwrados::account
diff --git a/src/rgw/driver/rados/account.h b/src/rgw/driver/rados/account.h
new file mode 100644 (file)
index 0000000..4bae822
--- /dev/null
@@ -0,0 +1,95 @@
+// -*- 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.
+ *
+ */
+
+#pragma once
+
+#include <list>
+#include <map>
+#include <string>
+#include "include/encoding.h"
+#include "common/async/yield_context.h"
+
+namespace ceph { class Formatter; }
+class DoutPrefixProvider;
+class JSONObj;
+struct rgw_raw_obj;
+class RGWAccountInfo;
+struct RGWBucketInfo;
+class RGWObjVersionTracker;
+class RGWSI_SysObj;
+class RGWStorageStats;
+class RGWZoneParams;
+
+namespace rgwrados::account {
+
+/// Return the rados object that tracks the given account's buckets. This
+/// can be used with the cls_user interface in namespace rgwrados::buckets.
+rgw_raw_obj get_buckets_obj(const RGWZoneParams& zone,
+                            std::string_view account_id);
+
+
+/// Read account info by id
+int read(const DoutPrefixProvider* dpp,
+         optional_yield y,
+         RGWSI_SysObj& sysobj,
+         const RGWZoneParams& zone,
+         std::string_view account_id,
+         RGWAccountInfo& info,
+         std::map<std::string, ceph::buffer::list>& attrs,
+         ceph::real_time& mtime,
+         RGWObjVersionTracker& objv);
+
+/// Read account info by name
+int read_by_name(const DoutPrefixProvider* dpp,
+                 optional_yield y,
+                 RGWSI_SysObj& sysobj,
+                 const RGWZoneParams& zone,
+                 std::string_view tenant,
+                 std::string_view name,
+                 RGWAccountInfo& info,
+                 std::map<std::string, ceph::buffer::list>& attrs,
+                 RGWObjVersionTracker& objv);
+
+/// Read account info by email
+int read_by_email(const DoutPrefixProvider* dpp,
+                  optional_yield y,
+                  RGWSI_SysObj& sysobj,
+                  const RGWZoneParams& zone,
+                  std::string_view email,
+                  RGWAccountInfo& info,
+                  std::map<std::string, ceph::buffer::list>& attrs,
+                  RGWObjVersionTracker& objv);
+
+/// Write account info and update name/email indices
+int write(const DoutPrefixProvider* dpp,
+          optional_yield y,
+          RGWSI_SysObj& sysobj,
+          const RGWZoneParams& zone,
+          const RGWAccountInfo& info,
+          const RGWAccountInfo* old_info,
+          const std::map<std::string, ceph::buffer::list>& attrs,
+          ceph::real_time mtime,
+          bool exclusive,
+          RGWObjVersionTracker& objv);
+
+/// Remove account info and name/email indices
+int remove(const DoutPrefixProvider* dpp,
+           optional_yield y,
+           RGWSI_SysObj& sysobj,
+           const RGWZoneParams& zone,
+           const RGWAccountInfo& info,
+           RGWObjVersionTracker& objv);
+
+} // namespace rgwrados::account
index 3f652950380b60020f56adad4b7e13bd765056eb..b6aab98aae2e37264517f4d2508e97a510cc92a8 100644 (file)
@@ -43,6 +43,7 @@
 #include "rgw_service.h"
 #include "rgw_lc.h"
 #include "rgw_lc_tier.h"
+#include "rgw_mdlog.h"
 #include "rgw_rest_admin.h"
 #include "rgw_rest_bucket.h"
 #include "rgw_rest_metadata.h"
@@ -52,6 +53,7 @@
 #include "rgw_rest_realm.h"
 #include "rgw_rest_user.h"
 #include "services/svc_sys_obj.h"
+#include "services/svc_mdlog.h"
 #include "services/svc_meta.h"
 #include "services/svc_meta_be_sobj.h"
 #include "services/svc_cls.h"
@@ -65,6 +67,7 @@
 #include "services/svc_sys_obj_cache.h"
 #include "cls/rgw/cls_rgw_client.h"
 
+#include "account.h"
 #include "rgw_pubsub.h"
 #include "topic.h"
 
@@ -1010,7 +1013,11 @@ int RadosStore::load_account_by_id(const DoutPrefixProvider* dpp,
                                    Attrs& attrs,
                                    RGWObjVersionTracker& objv)
 {
-  return -ENOTSUP;
+  ceph::real_time mtime; // ignored
+  return rgwrados::account::read(
+      dpp, y, *svc()->sysobj,
+      svc()->zone->get_zone_params(),
+      id, info, attrs, mtime, objv);
 }
 
 int RadosStore::load_account_by_name(const DoutPrefixProvider* dpp,
@@ -1021,7 +1028,10 @@ int RadosStore::load_account_by_name(const DoutPrefixProvider* dpp,
                                      Attrs& attrs,
                                      RGWObjVersionTracker& objv)
 {
-  return -ENOTSUP;
+  return rgwrados::account::read_by_name(
+      dpp, y, *svc()->sysobj,
+      svc()->zone->get_zone_params(),
+      tenant, name, info, attrs, objv);
 }
 
 int RadosStore::load_account_by_email(const DoutPrefixProvider* dpp,
@@ -1031,7 +1041,28 @@ int RadosStore::load_account_by_email(const DoutPrefixProvider* dpp,
                                       Attrs& attrs,
                                       RGWObjVersionTracker& objv)
 {
-  return -ENOTSUP;
+  return rgwrados::account::read_by_email(
+      dpp, y, *svc()->sysobj,
+      svc()->zone->get_zone_params(),
+      email, info, attrs, objv);
+}
+
+static int write_mdlog_entry(const DoutPrefixProvider* dpp, optional_yield y,
+                             RGWSI_MDLog& mdlog_svc,
+                             const std::string& section,
+                             const std::string& key,
+                             const RGWObjVersionTracker& objv)
+{
+  RGWMetadataLogData entry;
+  entry.read_version = objv.read_version;
+  entry.write_version = objv.write_version;
+  entry.status = MDLOG_STATUS_COMPLETE;
+
+  bufferlist bl;
+  encode(entry, bl);
+
+  const std::string hash_key = fmt::format("{}:{}", section, key);
+  return mdlog_svc.add_entry(dpp, hash_key, section, key, bl, y);
 }
 
 int RadosStore::store_account(const DoutPrefixProvider* dpp,
@@ -1041,7 +1072,15 @@ int RadosStore::store_account(const DoutPrefixProvider* dpp,
                               const Attrs& attrs,
                               RGWObjVersionTracker& objv)
 {
-  return -ENOTSUP;
+  ceph::real_time mtime = ceph::real_clock::now();
+  int r = rgwrados::account::write(
+      dpp, y, *svc()->sysobj, svc()->zone->get_zone_params(),
+      info, old_info, attrs, mtime, exclusive, objv);
+  if (r < 0) {
+    return r;
+  }
+
+  return write_mdlog_entry(dpp, y, *svc()->mdlog, "account", info.id, objv);
 }
 
 int RadosStore::delete_account(const DoutPrefixProvider* dpp,
@@ -1049,7 +1088,15 @@ int RadosStore::delete_account(const DoutPrefixProvider* dpp,
                                const RGWAccountInfo& info,
                                RGWObjVersionTracker& objv)
 {
-  return -ENOTSUP;
+  int r = rgwrados::account::remove(
+      dpp, y, *svc()->sysobj,
+      svc()->zone->get_zone_params(),
+      info, objv);
+  if (r < 0) {
+    return r;
+  }
+
+  return write_mdlog_entry(dpp, y, *svc()->mdlog, "account", info.id, objv);
 }
 
 int RadosStore::load_account_stats(const DoutPrefixProvider* dpp,
index c4585413a3b3a37608d93e7e90c5a0a5b03b6b37..373b672e6eb47f26478045308ae1c29b10568c03 100644 (file)
@@ -225,10 +225,10 @@ struct RGWUserAdminOpState {
     overwrite_new_user = b;
   }
 
-  void set_user_email(std::string& email) {
+  void set_user_email(const std::string& email) {
    /* always lowercase email address */
-    boost::algorithm::to_lower(email);
     user_email = email;
+    boost::algorithm::to_lower(user_email);
     user_email_specified = true;
   }