From: Casey Bodley Date: Tue, 13 Sep 2022 15:47:46 +0000 (-0400) Subject: rgw: add rgw::sal::ImmutableConfigStore for json-based config X-Git-Tag: v18.1.0~1101^2~5 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=465b6f2eb177051e02111f90256feac76d3659e5;p=ceph.git rgw: add rgw::sal::ImmutableConfigStore for json-based config Signed-off-by: Casey Bodley --- diff --git a/src/rgw/CMakeLists.txt b/src/rgw/CMakeLists.txt index 6713512d677..02c1fa57558 100644 --- a/src/rgw/CMakeLists.txt +++ b/src/rgw/CMakeLists.txt @@ -176,6 +176,8 @@ set(librgw_common_srcs rgw_lua_background.cc) list(APPEND librgw_common_srcs + store/immutable_config/store.cc + store/json_config/store.cc store/rados/config/impl.cc store/rados/config/period.cc store/rados/config/period_config.cc diff --git a/src/rgw/store/immutable_config/store.cc b/src/rgw/store/immutable_config/store.cc new file mode 100644 index 00000000000..8d3e0765faa --- /dev/null +++ b/src/rgw/store/immutable_config/store.cc @@ -0,0 +1,422 @@ +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2022 Red Hat, Inc. + * + * 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 "rgw_zone.h" +#include "store.h" + +namespace rgw::sal { + +ImmutableConfigStore::ImmutableConfigStore(const RGWZoneGroup& zonegroup, + const RGWZoneParams& zone, + const RGWPeriodConfig& period_config) + : zonegroup(zonegroup), zone(zone), period_config(period_config) +{ +} + +// Realm +int ImmutableConfigStore::write_default_realm_id(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + std::string_view realm_id) +{ + return -EROFS; +} + +int ImmutableConfigStore::read_default_realm_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string& realm_id) +{ + return -ENOENT; +} + +int ImmutableConfigStore::delete_default_realm_id(const DoutPrefixProvider* dpp, + optional_yield y) +{ + return -EROFS; +} + + +int ImmutableConfigStore::create_realm(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + const RGWRealm& info, + std::unique_ptr* writer) +{ + return -EROFS; +} + +int ImmutableConfigStore::read_realm_by_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + RGWRealm& info, + std::unique_ptr* writer) +{ + return -ENOENT; +} + +int ImmutableConfigStore::read_realm_by_name(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_name, + RGWRealm& info, + std::unique_ptr* writer) +{ + return -ENOENT; +} + +int ImmutableConfigStore::read_default_realm(const DoutPrefixProvider* dpp, + optional_yield y, + RGWRealm& info, + std::unique_ptr* writer) +{ + return -ENOENT; +} + +int ImmutableConfigStore::read_realm_id(const DoutPrefixProvider* dpp, + optional_yield y, std::string_view realm_name, + std::string& realm_id) +{ + return -ENOENT; +} + +int ImmutableConfigStore::realm_notify_new_period(const DoutPrefixProvider* dpp, + optional_yield y, + const RGWPeriod& period) +{ + return -ENOTSUP; +} + +int ImmutableConfigStore::list_realm_names(const DoutPrefixProvider* dpp, + optional_yield y, const std::string& marker, + std::span entries, + ListResult& result) +{ + result.next.clear(); + result.entries = entries.first(0); + return 0; +} + + +// Period +int ImmutableConfigStore::create_period(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + const RGWPeriod& info) +{ + return -EROFS; +} + +int ImmutableConfigStore::read_period(const DoutPrefixProvider* dpp, + optional_yield y, std::string_view period_id, + std::optional epoch, RGWPeriod& info) +{ + return -ENOENT; +} + +int ImmutableConfigStore::delete_period(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view period_id) +{ + return -EROFS; +} + +int ImmutableConfigStore::list_period_ids(const DoutPrefixProvider* dpp, + optional_yield y, const std::string& marker, + std::span entries, + ListResult& result) +{ + result.next.clear(); + result.entries = entries.first(0); + return 0; +} + + +// ZoneGroup + +class ImmutableZoneGroupWriter : public ZoneGroupWriter { + public: + int write(const DoutPrefixProvider* dpp, optional_yield y, + const RGWZoneGroup& info) override + { + return -EROFS; + } + int rename(const DoutPrefixProvider* dpp, optional_yield y, + RGWZoneGroup& info, std::string_view new_name) override + { + return -EROFS; + } + int remove(const DoutPrefixProvider* dpp, optional_yield y) override + { + return -EROFS; + } +}; + +int ImmutableConfigStore::write_default_zonegroup_id(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + std::string_view realm_id, + std::string_view zonegroup_id) +{ + return -EROFS; +} + +int ImmutableConfigStore::read_default_zonegroup_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + std::string& zonegroup_id) +{ + if (!realm_id.empty()) { + return -ENOENT; + } + zonegroup_id = zonegroup.id; + return 0; +} + +int ImmutableConfigStore::delete_default_zonegroup_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id) +{ + return -EROFS; +} + + +int ImmutableConfigStore::create_zonegroup(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + const RGWZoneGroup& info, + std::unique_ptr* writer) +{ + return -EROFS; +} + +int ImmutableConfigStore::read_zonegroup_by_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view zonegroup_id, + RGWZoneGroup& info, + std::unique_ptr* writer) +{ + if (zonegroup_id != zonegroup.id) { + return -ENOENT; + } + + info = zonegroup; + + if (writer) { + *writer = std::make_unique(); + } + return 0; +} +int ImmutableConfigStore::read_zonegroup_by_name(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view zonegroup_name, + RGWZoneGroup& info, + std::unique_ptr* writer) +{ + if (zonegroup_name != zonegroup.name) { + return -ENOENT; + } + + info = zonegroup; + + if (writer) { + *writer = std::make_unique(); + } + return 0; +} + +int ImmutableConfigStore::read_default_zonegroup(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + RGWZoneGroup& info, + std::unique_ptr* writer) +{ + info = zonegroup; + + if (writer) { + *writer = std::make_unique(); + } + return 0; +} + +int ImmutableConfigStore::list_zonegroup_names(const DoutPrefixProvider* dpp, + optional_yield y, const std::string& marker, + std::span entries, + ListResult& result) +{ + if (marker < zonegroup.name) { + entries[0] = zonegroup.name; + result.next = zonegroup.name; + result.entries = entries.first(1); + } else { + result.next.clear(); + result.entries = entries.first(0); + } + return 0; +} + +// Zone + +class ImmutableZoneWriter : public ZoneWriter { + public: + int write(const DoutPrefixProvider* dpp, optional_yield y, + const RGWZoneParams& info) override + { + return -EROFS; + } + int rename(const DoutPrefixProvider* dpp, optional_yield y, + RGWZoneParams& info, std::string_view new_name) override + { + return -EROFS; + } + int remove(const DoutPrefixProvider* dpp, optional_yield y) override + { + return -EROFS; + } +}; + +int ImmutableConfigStore::write_default_zone_id(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + std::string_view realm_id, + std::string_view zone_id) +{ + return -EROFS; +} + +int ImmutableConfigStore::read_default_zone_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + std::string& zone_id) +{ + if (realm_id.empty()) { + return -ENOENT; + } + zone_id = zone.id; + return 0; +} + +int ImmutableConfigStore::delete_default_zone_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id) +{ + return -EROFS; +} + + +int ImmutableConfigStore::create_zone(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + const RGWZoneParams& info, + std::unique_ptr* writer) +{ + return -EROFS; +} + +int ImmutableConfigStore::read_zone_by_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view zone_id, + RGWZoneParams& info, + std::unique_ptr* writer) +{ + if (zone_id != zone.id) { + return -ENOENT; + } + + info = zone; + + if (writer) { + *writer = std::make_unique(); + } + return 0; +} + +int ImmutableConfigStore::read_zone_by_name(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view zone_name, + RGWZoneParams& info, + std::unique_ptr* writer) +{ + if (zone_name != zone.name) { + return -ENOENT; + } + + info = zone; + + if (writer) { + *writer = std::make_unique(); + } + return 0; +} + +int ImmutableConfigStore::read_default_zone(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + RGWZoneParams& info, + std::unique_ptr* writer) +{ + if (!realm_id.empty()) { + return -ENOENT; + } + + info = zone; + + if (writer) { + *writer = std::make_unique(); + } + return 0; +} + +int ImmutableConfigStore::list_zone_names(const DoutPrefixProvider* dpp, + optional_yield y, const std::string& marker, + std::span entries, + ListResult& result) +{ + if (marker < zone.name) { + entries[0] = zone.name; + result.next = zone.name; + result.entries = entries.first(1); + } else { + result.next.clear(); + result.entries = entries.first(0); + } + return 0; +} + + +// PeriodConfig +int ImmutableConfigStore::read_period_config(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + RGWPeriodConfig& info) +{ + if (!realm_id.empty()) { + return -ENOENT; + } + + info = period_config; + return 0; +} + +int ImmutableConfigStore::write_period_config(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + std::string_view realm_id, + const RGWPeriodConfig& info) +{ + return -EROFS; +} + + +/// ImmutableConfigStore factory function +auto create_immutable_config_store(const DoutPrefixProvider* dpp, + const RGWZoneGroup& zonegroup, + const RGWZoneParams& zone, + const RGWPeriodConfig& period_config) + -> std::unique_ptr +{ + return std::make_unique(zonegroup, zone, period_config); +} + +} // namespace rgw::sal diff --git a/src/rgw/store/immutable_config/store.h b/src/rgw/store/immutable_config/store.h new file mode 100644 index 00000000000..9a1ac5f1443 --- /dev/null +++ b/src/rgw/store/immutable_config/store.h @@ -0,0 +1,180 @@ +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2022 Red Hat, Inc. + * + * 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 "rgw_sal_config.h" + +namespace rgw::sal { + +/// A read-only ConfigStore that serves the given default zonegroup and zone. +class ImmutableConfigStore : public ConfigStore { + public: + explicit ImmutableConfigStore(const RGWZoneGroup& zonegroup, + const RGWZoneParams& zone, + const RGWPeriodConfig& period_config); + + // Realm + virtual int write_default_realm_id(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + std::string_view realm_id) override; + virtual int read_default_realm_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string& realm_id) override; + virtual int delete_default_realm_id(const DoutPrefixProvider* dpp, + optional_yield y) override; + + virtual int create_realm(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + const RGWRealm& info, + std::unique_ptr* writer) override; + virtual int read_realm_by_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + RGWRealm& info, + std::unique_ptr* writer) override; + virtual int read_realm_by_name(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_name, + RGWRealm& info, + std::unique_ptr* writer) override; + virtual int read_default_realm(const DoutPrefixProvider* dpp, + optional_yield y, + RGWRealm& info, + std::unique_ptr* writer) override; + virtual int read_realm_id(const DoutPrefixProvider* dpp, + optional_yield y, std::string_view realm_name, + std::string& realm_id) override; + virtual int realm_notify_new_period(const DoutPrefixProvider* dpp, + optional_yield y, + const RGWPeriod& period) override; + virtual int list_realm_names(const DoutPrefixProvider* dpp, + optional_yield y, const std::string& marker, + std::span entries, + ListResult& result) override; + + // Period + virtual int create_period(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + const RGWPeriod& info) override; + virtual int read_period(const DoutPrefixProvider* dpp, + optional_yield y, std::string_view period_id, + std::optional epoch, RGWPeriod& info) override; + virtual int delete_period(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view period_id) override; + virtual int list_period_ids(const DoutPrefixProvider* dpp, + optional_yield y, const std::string& marker, + std::span entries, + ListResult& result) override; + + // ZoneGroup + virtual int write_default_zonegroup_id(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + std::string_view realm_id, + std::string_view zonegroup_id) override; + virtual int read_default_zonegroup_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + std::string& zonegroup_id) override; + virtual int delete_default_zonegroup_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id) override; + + virtual int create_zonegroup(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + const RGWZoneGroup& info, + std::unique_ptr* writer) override; + virtual int read_zonegroup_by_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view zonegroup_id, + RGWZoneGroup& info, + std::unique_ptr* writer) override; + virtual int read_zonegroup_by_name(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view zonegroup_name, + RGWZoneGroup& info, + std::unique_ptr* writer) override; + virtual int read_default_zonegroup(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + RGWZoneGroup& info, + std::unique_ptr* writer) override; + virtual int list_zonegroup_names(const DoutPrefixProvider* dpp, + optional_yield y, const std::string& marker, + std::span entries, + ListResult& result) override; + + // Zone + virtual int write_default_zone_id(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + std::string_view realm_id, + std::string_view zone_id) override; + virtual int read_default_zone_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + std::string& zone_id) override; + virtual int delete_default_zone_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id) override; + + virtual int create_zone(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + const RGWZoneParams& info, + std::unique_ptr* writer) override; + virtual int read_zone_by_id(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view zone_id, + RGWZoneParams& info, + std::unique_ptr* writer) override; + virtual int read_zone_by_name(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view zone_name, + RGWZoneParams& info, + std::unique_ptr* writer) override; + virtual int read_default_zone(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + RGWZoneParams& info, + std::unique_ptr* writer) override; + virtual int list_zone_names(const DoutPrefixProvider* dpp, + optional_yield y, const std::string& marker, + std::span entries, + ListResult& result) override; + + // PeriodConfig + virtual int read_period_config(const DoutPrefixProvider* dpp, + optional_yield y, + std::string_view realm_id, + RGWPeriodConfig& info) override; + virtual int write_period_config(const DoutPrefixProvider* dpp, + optional_yield y, bool exclusive, + std::string_view realm_id, + const RGWPeriodConfig& info) override; + + private: + const RGWZoneGroup zonegroup; + const RGWZoneParams zone; + const RGWPeriodConfig period_config; +}; // ImmutableConfigStore + + +/// ImmutableConfigStore factory function +auto create_immutable_config_store(const DoutPrefixProvider* dpp, + const RGWZoneGroup& zonegroup, + const RGWZoneParams& zone, + const RGWPeriodConfig& period_config) + -> std::unique_ptr; + +} // namespace rgw::sal diff --git a/src/rgw/store/json_config/store.cc b/src/rgw/store/json_config/store.cc new file mode 100644 index 00000000000..49837a8eacd --- /dev/null +++ b/src/rgw/store/json_config/store.cc @@ -0,0 +1,176 @@ +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2022 Red Hat, Inc. + * + * 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 +#include "include/buffer.h" +#include "common/errno.h" +#include "common/ceph_json.h" +#include "rgw_zone.h" +#include "store/immutable_config/store.h" +#include "store.h" + +namespace rgw::sal { + +namespace { + +struct DecodedConfig { + RGWZoneGroup zonegroup; + RGWZoneParams zone; + RGWPeriodConfig period_config; + + void decode_json(JSONObj *obj) + { + JSONDecoder::decode_json("zonegroup", zonegroup, obj); + JSONDecoder::decode_json("zone", zone, obj); + JSONDecoder::decode_json("period_config", period_config, obj); + } +}; + +static void parse_config(const DoutPrefixProvider* dpp, const char* filename) +{ + bufferlist bl; + std::string errmsg; + int r = bl.read_file(filename, &errmsg); + if (r < 0) { + ldpp_dout(dpp, 0) << "failed to read json config file '" << filename + << "': " << errmsg << dendl; + throw std::system_error(-r, std::system_category()); + } + + JSONParser p; + if (!p.parse(bl.c_str(), bl.length())) { + ldpp_dout(dpp, 0) << "failed to parse json config file" << dendl; + throw std::system_error(make_error_code(std::errc::invalid_argument)); + } + + DecodedConfig config; + try { + decode_json_obj(config, &p); + } catch (const JSONDecoder::err& e) { + ldpp_dout(dpp, 0) << "failed to decode JSON input: " << e.what() << dendl; + throw std::system_error(make_error_code(std::errc::invalid_argument)); + } +} + +void sanity_check_config(const DoutPrefixProvider* dpp, DecodedConfig& config) +{ + if (config.zonegroup.id.empty()) { + config.zonegroup.id = "default"; + } + if (config.zonegroup.name.empty()) { + config.zonegroup.name = "default"; + } + if (config.zonegroup.api_name.empty()) { + config.zonegroup.api_name = config.zonegroup.name; + } + + if (config.zone.id.empty()) { + config.zone.id = "default"; + } + if (config.zone.name.empty()) { + config.zone.name = "default"; + } + + // add default placement if it doesn't exist + rgw_pool pool; + RGWZonePlacementInfo placement; + placement.storage_classes.set_storage_class( + RGW_STORAGE_CLASS_STANDARD, &pool, nullptr); + config.zone.placement_pools.emplace("default-placement", + std::move(placement)); + + std::set pools; + int r = rgw::init_zone_pool_names(dpp, null_yield, pools, config.zone); + if (r < 0) { + ldpp_dout(dpp, 0) << "failed to set default zone pool names" << dendl; + throw std::system_error(-r, std::system_category()); + } + + // verify that config.zonegroup only contains config.zone + if (config.zonegroup.zones.size() > 1) { + ldpp_dout(dpp, 0) << "zonegroup cannot contain multiple zones" << dendl; + throw std::system_error(make_error_code(std::errc::invalid_argument)); + } + + if (config.zonegroup.zones.size() == 1) { + auto z = config.zonegroup.zones.begin(); + if (z->first != config.zone.id) { + ldpp_dout(dpp, 0) << "zonegroup contains unknown zone id=" + << z->first << dendl; + throw std::system_error(make_error_code(std::errc::invalid_argument)); + } + if (z->second.id != config.zone.id) { + ldpp_dout(dpp, 0) << "zonegroup contains unknown zone id=" + << z->second.id << dendl; + throw std::system_error(make_error_code(std::errc::invalid_argument)); + } + if (z->second.name != config.zone.name) { + ldpp_dout(dpp, 0) << "zonegroup contains unknown zone name=" + << z->second.name << dendl; + throw std::system_error(make_error_code(std::errc::invalid_argument)); + } + if (config.zonegroup.master_zone != config.zone.id) { + ldpp_dout(dpp, 0) << "zonegroup contains unknown master_zone=" + << config.zonegroup.master_zone << dendl; + throw std::system_error(make_error_code(std::errc::invalid_argument)); + } + } else { + // add the zone to the group + const bool is_master = true; + const bool read_only = false; + std::list endpoints; + std::list sync_from; + std::list sync_from_rm; + rgw::zone_features::set enable_features; + rgw::zone_features::set disable_features; + + enable_features.insert(rgw::zone_features::supported.begin(), + rgw::zone_features::supported.end()); + + int r = rgw::add_zone_to_group(dpp, config.zonegroup, config.zone, + &is_master, &read_only, endpoints, + nullptr, nullptr, sync_from, sync_from_rm, + nullptr, std::nullopt, + enable_features, disable_features); + if (r < 0) { + ldpp_dout(dpp, 0) << "failed to add zone to zonegroup: " + << cpp_strerror(r) << dendl; + throw std::system_error(-r, std::system_category()); + } + + config.zonegroup.enabled_features = std::move(enable_features); + } + + // insert the default placement target if it doesn't exist + auto target = RGWZoneGroupPlacementTarget{.name = "default-placement"}; + config.zonegroup.placement_targets.emplace(target.name, target); + if (config.zonegroup.default_placement.name.empty()) { + config.zonegroup.default_placement.name = target.name; + } +} + +} // anonymous namespace + +auto create_json_config_store(const DoutPrefixProvider* dpp, + const std::string& filename) + -> std::unique_ptr +{ + DecodedConfig config; + parse_config(dpp, filename.c_str()); + sanity_check_config(dpp, config); + return create_immutable_config_store(dpp, config.zonegroup, config.zone, + config.period_config); +} + +} // namespace rgw::sal diff --git a/src/rgw/store/json_config/store.h b/src/rgw/store/json_config/store.h new file mode 100644 index 00000000000..63ddf6fdcca --- /dev/null +++ b/src/rgw/store/json_config/store.h @@ -0,0 +1,27 @@ +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2022 Red Hat, Inc. + * + * 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 "store/immutable_config/store.h" + +namespace rgw::sal { + +/// Create an immutable ConfigStore by parsing the zonegroup and zone from the +/// given json filename. +auto create_json_config_store(const DoutPrefixProvider* dpp, + const std::string& filename) + -> std::unique_ptr; + +} // namespace rgw::sal