From 8c3fc16b3d9ef866c6b9cee558b30d78b357c8cc Mon Sep 17 00:00:00 2001 From: Casey Bodley Date: Mon, 27 Nov 2023 18:12:22 -0500 Subject: [PATCH] rgw/rados: implement account metadata operations Signed-off-by: Casey Bodley --- src/rgw/CMakeLists.txt | 1 + src/rgw/driver/rados/account.cc | 391 ++++++++++++++++++++++++++ src/rgw/driver/rados/account.h | 95 +++++++ src/rgw/driver/rados/rgw_sal_rados.cc | 57 +++- src/rgw/driver/rados/rgw_user.h | 4 +- 5 files changed, 541 insertions(+), 7 deletions(-) create mode 100644 src/rgw/driver/rados/account.cc create mode 100644 src/rgw/driver/rados/account.h diff --git a/src/rgw/CMakeLists.txt b/src/rgw/CMakeLists.txt index ed21cfba9c1..ef4d24b0c3b 100644 --- a/src/rgw/CMakeLists.txt +++ b/src/rgw/CMakeLists.txt @@ -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 index 00000000000..acc4dee0c23 --- /dev/null +++ b/src/rgw/driver/rados/account.cc @@ -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 +#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& 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& 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& 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& 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 remove_name; + std::optional 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 index 00000000000..4bae82271f0 --- /dev/null +++ b/src/rgw/driver/rados/account.h @@ -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 +#include +#include +#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& 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& 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& 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& 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 diff --git a/src/rgw/driver/rados/rgw_sal_rados.cc b/src/rgw/driver/rados/rgw_sal_rados.cc index 3f652950380..b6aab98aae2 100644 --- a/src/rgw/driver/rados/rgw_sal_rados.cc +++ b/src/rgw/driver/rados/rgw_sal_rados.cc @@ -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, diff --git a/src/rgw/driver/rados/rgw_user.h b/src/rgw/driver/rados/rgw_user.h index c4585413a3b..373b672e6eb 100644 --- a/src/rgw/driver/rados/rgw_user.h +++ b/src/rgw/driver/rados/rgw_user.h @@ -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; } -- 2.39.5