// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
-// vim: ts=8 sw=2 smarttab
+// vim: ts=8 sw=2 smarttab ft=cpp
#include <string.h>
#include <iostream>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/predicate.hpp>
#include "common/Formatter.h"
#include <common/errno.h>
#include "rgw_lc.h"
#include "rgw_zone.h"
#include "rgw_string.h"
+#include "rgw_multi.h"
+
+// this seems safe to use, at least for now--arguably, we should
+// prefer header-only fmt, in general
+#undef FMT_HEADER_ONLY
+#define FMT_HEADER_ONLY 1
+#include "fmt/format.h"
#include "services/svc_sys_obj.h"
+#include "services/svc_zone.h"
+#include "services/svc_tier_rados.h"
#define dout_context g_ceph_context
#define dout_subsys ceph_subsys_rgw
if (rule_map.find(id) != rule_map.end()) { //id shouldn't be the same
return -EINVAL;
}
+ if (rule.get_filter().has_tags() && (rule.get_dm_expiration() || !rule.get_mp_expiration().empty())) {
+ return -ERR_INVALID_REQUEST;
+ }
rule_map.insert(pair<string, LCRule>(id, rule));
if (!_add_rule(rule)) {
ldpp_dout(dpp, 5) << "schedule life cycle next start time: " << rgw_to_asctime(next) << dendl;
- lock.Lock();
- cond.WaitInterval(lock, utime_t(secs, 0));
- lock.Unlock();
+ std::unique_lock l{lock};
+ cond.wait_for(l, std::chrono::seconds(secs));
} while (!lc->going_down());
return NULL;
}
-void RGWLC::initialize(CephContext *_cct, RGWRados *_store) {
+void RGWLC::initialize(CephContext *_cct, rgw::sal::RGWRadosStore *_store) {
cct = _cct;
store = _store;
max_objs = cct->_conf->rgw_lc_max_objs;
#define MAX_LC_LIST_ENTRIES 100
do {
- int ret = cls_rgw_lc_list(store->lc_pool_ctx, obj_names[index], marker, MAX_LC_LIST_ENTRIES, entries);
+ int ret = cls_rgw_lc_list(store->getRados()->lc_pool_ctx, obj_names[index], marker, MAX_LC_LIST_ENTRIES, entries);
if (ret < 0)
return ret;
map<string, int>::iterator iter;
for (iter = entries.begin(); iter != entries.end(); ++iter) {
pair<string, int > entry(iter->first, lc_uninitial);
- ret = cls_rgw_lc_set_entry(store->lc_pool_ctx, obj_names[index], entry);
+ ret = cls_rgw_lc_set_entry(store->getRados()->lc_pool_ctx, obj_names[index], entry);
if (ret < 0) {
ldpp_dout(this, 0) << "RGWLC::bucket_lc_prepare() failed to set entry on "
<< obj_names[index] << dendl;
RGWRados::Object::Read read_op(&op_target);
map<string, bufferlist> attrs;
read_op.params.attrs = &attrs;
- int ret = read_op.prepare();
+ int ret = read_op.prepare(null_yield);
if (ret < 0) {
if (ret == -ENOENT) {
return true;
do {
objs.clear();
list_op.params.marker = list_op.get_next_marker();
- ret = list_op.list_objects(1000, &objs, NULL, &is_truncated);
+ ret = list_op.list_objects(1000, &objs, NULL, &is_truncated, null_yield);
if (ret < 0) {
if (ret == (-ENOENT))
return 0;
RGWRados::Object op_target(store, bucket_info, ctx, obj);
RGWRados::Object::Read read_op(&op_target);
- return read_op.get_attr(RGW_ATTR_TAGS, tags_bl);
+ return read_op.get_attr(RGW_ATTR_TAGS, tags_bl, null_yield);
}
static bool is_valid_op(const lc_op& op)
static inline bool has_all_tags(const lc_op& rule_action,
const RGWObjTags& object_tags)
{
+ if(! rule_action.obj_tags)
+ return false;
+ if(object_tags.count() < rule_action.obj_tags->count())
+ return false;
+ size_t tag_count = 0;
for (const auto& tag : object_tags.get_tags()) {
-
- if (! rule_action.obj_tags)
- return false;
-
const auto& rule_tags = rule_action.obj_tags->get_tags();
const auto& iter = rule_tags.find(tag.first);
-
- if ((iter == rule_tags.end()) ||
- (iter->second != tag.second))
- return false;
+ if(iter->second == tag.second)
+ {
+ tag_count++;
+ }
+ /* all tags in the rule appear in obj tags */
}
- /* all tags matched */
- return true;
+ return tag_count == rule_action.obj_tags->count();
}
class LCObjsLister {
- RGWRados *store;
+ rgw::sal::RGWRadosStore *store;
RGWBucketInfo& bucket_info;
RGWRados::Bucket target;
RGWRados::Bucket::List list_op;
int64_t delay_ms;
public:
- LCObjsLister(RGWRados *_store, RGWBucketInfo& _bucket_info) :
+ LCObjsLister(rgw::sal::RGWRadosStore *_store, RGWBucketInfo& _bucket_info) :
store(_store), bucket_info(_bucket_info),
- target(store, bucket_info), list_op(&target) {
+ target(store->getRados(), bucket_info), list_op(&target) {
list_op.params.list_versions = bucket_info.versioned();
list_op.params.allow_unordered = true;
delay_ms = store->ctx()->_conf.get_val<int64_t>("rgw_lc_thread_delay");
}
int fetch() {
- int ret = list_op.list_objects(1000, &objs, NULL, &is_truncated);
+ int ret = list_op.list_objects(1000, &objs, NULL, &is_truncated, null_yield);
if (ret < 0) {
return ret;
}
bool get_obj(rgw_bucket_dir_entry *obj) {
if (obj_iter == objs.end()) {
- delay();
- return false;
- }
- if (is_truncated && (obj_iter + 1)==objs.end()) {
- list_op.params.marker = obj_iter->key;
-
- int ret = fetch();
- if (ret < 0) {
- ldout(store->ctx(), 0) << "ERROR: list_op returned ret=" << ret << dendl;
- return ret;
+ if (!is_truncated) {
+ delay();
+ return false;
} else {
- obj_iter = objs.begin();
+ list_op.params.marker = pre_obj.key;
+
+ int ret = fetch();
+ if (ret < 0) {
+ ldout(store->ctx(), 0) << "ERROR: list_op returned ret=" << ret << dendl;
+ return ret;
+ }
}
delay();
}
*obj = *obj_iter;
- return true;
+ return obj_iter != objs.end();
}
rgw_bucket_dir_entry get_prev_obj() {
struct op_env {
lc_op& op;
- RGWRados *store;
+ rgw::sal::RGWRadosStore *store;
RGWLC *lc;
RGWBucketInfo& bucket_info;
LCObjsLister& ol;
- op_env(lc_op& _op, RGWRados *_store, RGWLC *_lc, RGWBucketInfo& _bucket_info,
+ op_env(lc_op& _op, rgw::sal::RGWRadosStore *_store, RGWLC *_lc, RGWBucketInfo& _bucket_info,
LCObjsLister& _ol) : op(_op), store(_store), lc(_lc), bucket_info(_bucket_info), ol(_ol) {}
};
op_env& env;
rgw_bucket_dir_entry& o;
- RGWRados *store;
+ rgw::sal::RGWRadosStore *store;
RGWBucketInfo& bucket_info;
lc_op& op;
LCObjsLister& ol;
rgw_obj obj;
RGWObjectCtx rctx;
+ const DoutPrefixProvider *dpp;
- lc_op_ctx(op_env& _env, rgw_bucket_dir_entry& _o) : cct(_env.store->ctx()), env(_env), o(_o),
+ lc_op_ctx(op_env& _env, rgw_bucket_dir_entry& _o, const DoutPrefixProvider *_dpp) : cct(_env.store->ctx()), env(_env), o(_o),
store(env.store), bucket_info(env.bucket_info), op(env.op), ol(env.ol),
- obj(env.bucket_info.bucket, o.key), rctx(env.store) {}
+ obj(env.bucket_info.bucket, o.key), rctx(env.store), dpp(_dpp) {}
};
static int remove_expired_obj(lc_op_ctx& oc, bool remove_indeed)
obj_owner.set_id(rgw_user {meta.owner});
obj_owner.set_name(meta.owner_display_name);
- RGWRados::Object del_target(store, bucket_info, oc.rctx, obj);
+ RGWRados::Object del_target(store->getRados(), bucket_info, oc.rctx, obj);
RGWRados::Object::Delete del_op(&del_target);
del_op.params.bucket_owner = bucket_info.owner;
del_op.params.obj_owner = obj_owner;
del_op.params.unmod_since = meta.mtime;
- return del_op.delete_obj();
+ return del_op.delete_obj(null_yield);
}
class LCOpAction {
LCOpRule(op_env& _env) : env(_env) {}
void build();
- int process(rgw_bucket_dir_entry& o);
+ int process(rgw_bucket_dir_entry& o, const DoutPrefixProvider *dpp);
};
static int check_tags(lc_op_ctx& oc, bool *skip)
*skip = true;
bufferlist tags_bl;
- int ret = read_obj_tags(oc.store, oc.bucket_info, oc.obj, oc.rctx, tags_bl);
+ int ret = read_obj_tags(oc.store->getRados(), oc.bucket_info, oc.obj, oc.rctx, tags_bl);
if (ret < 0) {
if (ret != -ENODATA) {
ldout(oc.cct, 5) << "ERROR: read_obj_tags returned r=" << ret << dendl;
}
if (! has_all_tags(op, dest_obj_tags)) {
- ldout(oc.cct, 20) << __func__ << "() skipping obj " << oc.obj << " as tags do not match" << dendl;
+ ldout(oc.cct, 20) << __func__ << "() skipping obj " << oc.obj << " as tags do not match in rule: " << op.id << dendl;
return 0;
}
}
r = remove_expired_obj(oc, !oc.bucket_info.versioned());
}
if (r < 0) {
- ldout(oc.cct, 0) << "ERROR: remove_expired_obj " << dendl;
+ ldout(oc.cct, 0) << "ERROR: remove_expired_obj "
+ << oc.bucket_info.bucket << ":" << o.key
+ << " " << cpp_strerror(r) << dendl;
return r;
}
ldout(oc.cct, 2) << "DELETED:" << oc.bucket_info.bucket << ":" << o.key << dendl;
bool is_expired = obj_has_expired(oc.cct, mtime, expiration, exp_time);
ldout(oc.cct, 20) << __func__ << "(): key=" << o.key << ": is_expired=" << is_expired << dendl;
- return is_expired && pass_object_lock_check(oc.store, oc.bucket_info, oc.obj, oc.rctx);
+ return is_expired && pass_object_lock_check(oc.store->getRados(), oc.bucket_info, oc.obj, oc.rctx);
}
int process(lc_op_ctx& oc) {
auto& o = oc.o;
int r = remove_expired_obj(oc, true);
if (r < 0) {
- ldout(oc.cct, 0) << "ERROR: remove_expired_obj " << dendl;
+ ldout(oc.cct, 0) << "ERROR: remove_expired_obj (non-current expiration) "
+ << oc.bucket_info.bucket << ":" << o.key
+ << " " << cpp_strerror(r) << dendl;
return r;
}
ldout(oc.cct, 2) << "DELETED:" << oc.bucket_info.bucket << ":" << o.key << " (non-current expiration)" << dendl;
auto& o = oc.o;
int r = remove_expired_obj(oc, true);
if (r < 0) {
- ldout(oc.cct, 0) << "ERROR: remove_expired_obj " << dendl;
+ ldout(oc.cct, 0) << "ERROR: remove_expired_obj (delete marker expiration) "
+ << oc.bucket_info.bucket << ":" << o.key
+ << " " << cpp_strerror(r) << dendl;
return r;
}
ldout(oc.cct, 2) << "DELETED:" << oc.bucket_info.bucket << ":" << o.key << " (delete marker expiration)" << dendl;
auto mtime = get_effective_mtime(oc);
bool is_expired;
- if (transition.days <= 0) {
+ if (transition.days < 0) {
if (transition.date == boost::none) {
ldout(oc.cct, 20) << __func__ << "(): key=" << o.key << ": no transition day/date set in rule, skipping" << dendl;
return false;
target_placement.inherit_from(oc.bucket_info.placement_rule);
target_placement.storage_class = transition.storage_class;
- if (!oc.store->svc.zone->get_zone_params().valid_placement(target_placement)) {
- ldout(oc.cct, 0) << "ERROR: non existent dest placement: " << target_placement
- << " bucket="<< oc.bucket_info.bucket
- << " rule_id=" << oc.op.id << dendl;
+ if (!oc.store->svc()->zone->get_zone_params().valid_placement(target_placement)) {
+ ldpp_dout(oc.dpp, 0) << "ERROR: non existent dest placement: " << target_placement
+ << " bucket="<< oc.bucket_info.bucket
+ << " rule_id=" << oc.op.id << dendl;
return -EINVAL;
}
- int r = oc.store->transition_obj(oc.rctx, oc.bucket_info, oc.obj,
- target_placement, o.meta.mtime, o.versioned_epoch);
+ int r = oc.store->getRados()->transition_obj(oc.rctx, oc.bucket_info, oc.obj,
+ target_placement, o.meta.mtime, o.versioned_epoch, oc.dpp, null_yield);
if (r < 0) {
- ldout(oc.cct, 0) << "ERROR: failed to transition obj (r=" << r << ")" << dendl;
+ ldpp_dout(oc.dpp, 0) << "ERROR: failed to transition obj "
+ << oc.bucket_info.bucket << ":" << o.key
+ << " -> " << transition.storage_class
+ << " " << cpp_strerror(r) << dendl;
return r;
}
- ldout(oc.cct, 2) << "TRANSITIONED:" << oc.bucket_info.bucket << ":" << o.key << " -> " << transition.storage_class << dendl;
+ ldpp_dout(oc.dpp, 2) << "TRANSITIONED:" << oc.bucket_info.bucket << ":" << o.key << " -> " << transition.storage_class << dendl;
return 0;
}
};
}
}
-int LCOpRule::process(rgw_bucket_dir_entry& o)
+int LCOpRule::process(rgw_bucket_dir_entry& o, const DoutPrefixProvider *dpp)
{
- lc_op_ctx ctx(env, o);
+ lc_op_ctx ctx(env, o, dpp);
unique_ptr<LCOpAction> *selected = nullptr;
real_time exp;
}
if (!cont) {
- ldout(env.store->ctx(), 20) << __func__ << "(): key=" << o.key << ": no rule match, skipping" << dendl;
+ ldpp_dout(dpp, 20) << __func__ << "(): key=" << o.key << ": no rule match, skipping" << dendl;
return 0;
}
int r = (*selected)->process(ctx);
if (r < 0) {
- ldout(ctx.cct, 0) << "ERROR: remove_expired_obj " << dendl;
+ ldpp_dout(dpp, 0) << "ERROR: remove_expired_obj "
+ << env.bucket_info.bucket << ":" << o.key
+ << " " << cpp_strerror(r) << dendl;
return r;
}
- ldout(ctx.cct, 20) << "processed:" << env.bucket_info.bucket << ":" << o.key << dendl;
+ ldpp_dout(dpp, 20) << "processed:" << env.bucket_info.bucket << ":" << o.key << dendl;
}
return 0;
map<string, bufferlist> bucket_attrs;
string no_ns, list_versions;
vector<rgw_bucket_dir_entry> objs;
- auto obj_ctx = store->svc.sysobj->init_obj_ctx();
vector<std::string> result;
boost::split(result, shard_id, boost::is_any_of(":"));
string bucket_tenant = result[0];
string bucket_name = result[1];
string bucket_marker = result[2];
- int ret = store->get_bucket_info(obj_ctx, bucket_tenant, bucket_name, bucket_info, NULL, &bucket_attrs);
+ int ret = store->getRados()->get_bucket_info(store->svc(), bucket_tenant, bucket_name, bucket_info, NULL, null_yield, &bucket_attrs);
if (ret < 0) {
ldpp_dout(this, 0) << "LC:get_bucket_info for " << bucket_name << " failed" << dendl;
return ret;
return -ENOENT;
}
- RGWRados::Bucket target(store, bucket_info);
+ RGWRados::Bucket target(store->getRados(), bucket_info);
map<string, bufferlist>::iterator aiter = bucket_attrs.find(RGW_ATTR_LC);
if (aiter == bucket_attrs.end())
orule.build();
- ceph::real_time mtime;
rgw_bucket_dir_entry o;
for (; ol.get_obj(&o); ol.next()) {
ldpp_dout(this, 20) << __func__ << "(): key=" << o.key << dendl;
- int ret = orule.process(o);
+ int ret = orule.process(o, this);
if (ret < 0) {
ldpp_dout(this, 20) << "ERROR: orule.process() returned ret="
<< ret
l.set_duration(lock_duration);
do {
- int ret = l.lock_exclusive(&store->lc_pool_ctx, obj_names[index]);
+ int ret = l.lock_exclusive(&store->getRados()->lc_pool_ctx, obj_names[index]);
if (ret == -EBUSY || ret == -EEXIST) { /* already locked by another lc processor */
ldpp_dout(this, 0) << "RGWLC::bucket_lc_post() failed to acquire lock on "
<< obj_names[index] << ", sleep 5, try again" << dendl;
return 0;
ldpp_dout(this, 20) << "RGWLC::bucket_lc_post() lock " << obj_names[index] << dendl;
if (result == -ENOENT) {
- ret = cls_rgw_lc_rm_entry(store->lc_pool_ctx, obj_names[index], entry);
+ ret = cls_rgw_lc_rm_entry(store->getRados()->lc_pool_ctx, obj_names[index], entry);
if (ret < 0) {
ldpp_dout(this, 0) << "RGWLC::bucket_lc_post() failed to remove entry "
<< obj_names[index] << dendl;
entry.second = lc_complete;
}
- ret = cls_rgw_lc_set_entry(store->lc_pool_ctx, obj_names[index], entry);
+ ret = cls_rgw_lc_set_entry(store->getRados()->lc_pool_ctx, obj_names[index], entry);
if (ret < 0) {
ldpp_dout(this, 0) << "RGWLC::process() failed to set entry on "
<< obj_names[index] << dendl;
}
clean:
- l.unlock(&store->lc_pool_ctx, obj_names[index]);
+ l.unlock(&store->getRados()->lc_pool_ctx, obj_names[index]);
ldpp_dout(this, 20) << "RGWLC::bucket_lc_post() unlock " << obj_names[index] << dendl;
return 0;
} while (true);
progress_map->clear();
for(; index <max_objs; index++) {
map<string, int > entries;
- int ret = cls_rgw_lc_list(store->lc_pool_ctx, obj_names[index], marker, max_entries, entries);
+ int ret = cls_rgw_lc_list(store->getRados()->lc_pool_ctx, obj_names[index], marker, max_entries, entries);
if (ret < 0) {
if (ret == -ENOENT) {
ldpp_dout(this, 10) << __func__ << "() ignoring unfound lc object="
utime_t time(max_lock_secs, 0);
l.set_duration(time);
- int ret = l.lock_exclusive(&store->lc_pool_ctx, obj_names[index]);
+ int ret = l.lock_exclusive(&store->getRados()->lc_pool_ctx, obj_names[index]);
if (ret == -EBUSY || ret == -EEXIST) { /* already locked by another lc processor */
ldpp_dout(this, 0) << "RGWLC::process() failed to acquire lock on "
<< obj_names[index] << ", sleep 5, try again" << dendl;
return 0;
cls_rgw_lc_obj_head head;
- ret = cls_rgw_lc_get_head(store->lc_pool_ctx, obj_names[index], head);
+ ret = cls_rgw_lc_get_head(store->getRados()->lc_pool_ctx, obj_names[index], head);
if (ret < 0) {
ldpp_dout(this, 0) << "RGWLC::process() failed to get obj head "
<< obj_names[index] << ", ret=" << ret << dendl;
}
}
- ret = cls_rgw_lc_get_next_entry(store->lc_pool_ctx, obj_names[index], head.marker, entry);
+ ret = cls_rgw_lc_get_next_entry(store->getRados()->lc_pool_ctx, obj_names[index], head.marker, entry);
if (ret < 0) {
ldpp_dout(this, 0) << "RGWLC::process() failed to get obj entry "
<< obj_names[index] << dendl;
goto exit;
entry.second = lc_processing;
- ret = cls_rgw_lc_set_entry(store->lc_pool_ctx, obj_names[index], entry);
+ ret = cls_rgw_lc_set_entry(store->getRados()->lc_pool_ctx, obj_names[index], entry);
if (ret < 0) {
ldpp_dout(this, 0) << "RGWLC::process() failed to set obj entry " << obj_names[index]
<< " (" << entry.first << "," << entry.second << ")" << dendl;
}
head.marker = entry.first;
- ret = cls_rgw_lc_put_head(store->lc_pool_ctx, obj_names[index], head);
+ ret = cls_rgw_lc_put_head(store->getRados()->lc_pool_ctx, obj_names[index], head);
if (ret < 0) {
ldpp_dout(this, 0) << "RGWLC::process() failed to put head " << obj_names[index] << dendl;
goto exit;
}
- l.unlock(&store->lc_pool_ctx, obj_names[index]);
+ l.unlock(&store->getRados()->lc_pool_ctx, obj_names[index]);
ret = bucket_lc_process(entry.first);
bucket_lc_post(index, max_lock_secs, entry, ret);
}while(1);
exit:
- l.unlock(&store->lc_pool_ctx, obj_names[index]);
+ l.unlock(&store->getRados()->lc_pool_ctx, obj_names[index]);
return 0;
}
void RGWLC::LCWorker::stop()
{
- Mutex::Locker l(lock);
- cond.Signal();
+ std::lock_guard l{lock};
+ cond.notify_all();
}
bool RGWLC::going_down()
}
template<typename F>
-static int guard_lc_modify(RGWRados* store, const rgw_bucket& bucket, const string& cookie, const F& f) {
+static int guard_lc_modify(rgw::sal::RGWRadosStore* store, const rgw_bucket& bucket, const string& cookie, const F& f) {
CephContext *cct = store->ctx();
string shard_id = get_lc_shard_name(bucket);
l.set_duration(time);
l.set_cookie(cookie);
- librados::IoCtx *ctx = store->get_lc_pool_ctx();
+ librados::IoCtx *ctx = store->getRados()->get_lc_pool_ctx();
int ret;
do {
attrs[RGW_ATTR_LC] = std::move(lc_bl);
- int ret = rgw_bucket_set_attrs(store, bucket_info, attrs, &bucket_info.objv_tracker);
+ int ret = store->ctl()->bucket->set_bucket_instance_attrs(bucket_info, attrs,
+ &bucket_info.objv_tracker,
+ null_yield);
if (ret < 0)
return ret;
{
map<string, bufferlist> attrs = bucket_attrs;
attrs.erase(RGW_ATTR_LC);
- int ret = rgw_bucket_set_attrs(store, bucket_info, attrs,
- &bucket_info.objv_tracker);
+ int ret = store->ctl()->bucket->set_bucket_instance_attrs(bucket_info, attrs,
+ &bucket_info.objv_tracker,
+ null_yield);
rgw_bucket& bucket = bucket_info.bucket;
namespace rgw::lc {
-int fix_lc_shard_entry(RGWRados* store, const RGWBucketInfo& bucket_info,
+int fix_lc_shard_entry(rgw::sal::RGWRadosStore* store, const RGWBucketInfo& bucket_info,
const map<std::string,bufferlist>& battrs)
{
if (auto aiter = battrs.find(RGW_ATTR_LC);
// 2. entry doesn't exist, which usually happens when reshard has happened prior to update and next LC process has already dropped the update
// 3. entry exists matching the current bucket id which was after a reshard (needs to be updated to the marker)
// We are not dropping the old marker here as that would be caught by the next LC process update
- auto lc_pool_ctx = store->get_lc_pool_ctx();
+ auto lc_pool_ctx = store->getRados()->get_lc_pool_ctx();
int ret = cls_rgw_lc_get_entry(*lc_pool_ctx,
lc_oid, shard_name, entry);
if (ret == 0) {
return ret;
}
-}
+std::string s3_expiration_header(
+ DoutPrefixProvider* dpp,
+ const rgw_obj_key& obj_key,
+ const RGWObjTags& obj_tagset,
+ const ceph::real_time& mtime,
+ const std::map<std::string, buffer::list>& bucket_attrs)
+{
+ CephContext* cct = dpp->get_cct();
+ RGWLifecycleConfiguration config(cct);
+ std::string hdr{""};
+
+ const auto& aiter = bucket_attrs.find(RGW_ATTR_LC);
+ if (aiter == bucket_attrs.end())
+ return hdr;
+
+ bufferlist::const_iterator iter{&aiter->second};
+ try {
+ config.decode(iter);
+ } catch (const buffer::error& e) {
+ ldpp_dout(dpp, 0) << __func__
+ << "() decode life cycle config failed"
+ << dendl;
+ return hdr;
+ } /* catch */
+
+ /* dump tags at debug level 16 */
+ RGWObjTags::tag_map_t obj_tag_map = obj_tagset.get_tags();
+ if (cct->_conf->subsys.should_gather(ceph_subsys_rgw, 16)) {
+ for (const auto& elt : obj_tag_map) {
+ ldout(cct, 16) << __func__
+ << "() key=" << elt.first << " val=" << elt.second
+ << dendl;
+ }
+ }
+
+ boost::optional<ceph::real_time> expiration_date;
+ boost::optional<std::string> rule_id;
+
+ const auto& rule_map = config.get_rule_map();
+ for (const auto& ri : rule_map) {
+ const auto& rule = ri.second;
+ auto& id = rule.get_id();
+ auto& prefix = rule.get_prefix();
+ auto& filter = rule.get_filter();
+ auto& expiration = rule.get_expiration();
+ auto& noncur_expiration = rule.get_noncur_expiration();
+
+ ldpp_dout(dpp, 10) << "rule: " << ri.first
+ << " prefix: " << prefix
+ << " expiration: "
+ << " date: " << expiration.get_date()
+ << " days: " << expiration.get_days()
+ << " noncur_expiration: "
+ << " date: " << noncur_expiration.get_date()
+ << " days: " << noncur_expiration.get_days()
+ << dendl;
+
+ /* skip if rule !enabled
+ * if rule has prefix, skip iff object !match prefix
+ * if rule has tags, skip iff object !match tags
+ * note if object is current or non-current, compare accordingly
+ * if rule has days, construct date expression and save iff older
+ * than last saved
+ * if rule has date, convert date expression and save iff older
+ * than last saved
+ * if the date accum has a value, format it into hdr
+ */
+
+ if (! rule.is_enabled())
+ continue;
+
+ if(! prefix.empty()) {
+ if (! boost::starts_with(obj_key.name, prefix))
+ continue;
+ }
+
+ if (filter.has_tags()) {
+ bool tag_match = false;
+ const RGWObjTags& rule_tagset = filter.get_tags();
+ for (auto& tag : rule_tagset.get_tags()) {
+ /* remember, S3 tags are {key,value} tuples */
+ auto ma1 = obj_tag_map.find(tag.first);
+ if ( ma1 != obj_tag_map.end()) {
+ if (tag.second == ma1->second) {
+ ldpp_dout(dpp, 10) << "tag match obj_key=" << obj_key
+ << " rule_id=" << id
+ << " tag=" << tag
+ << " (ma=" << *ma1 << ")"
+ << dendl;
+ tag_match = true;
+ break;
+ }
+ }
+ }
+ if (! tag_match)
+ continue;
+ }
+
+ // compute a uniform expiration date
+ boost::optional<ceph::real_time> rule_expiration_date;
+ const LCExpiration& rule_expiration =
+ (obj_key.instance.empty()) ? expiration : noncur_expiration;
+
+ if (rule_expiration.has_date()) {
+ rule_expiration_date =
+ boost::optional<ceph::real_time>(
+ ceph::from_iso_8601(rule.get_expiration().get_date()));
+ } else {
+ if (rule_expiration.has_days()) {
+ rule_expiration_date =
+ boost::optional<ceph::real_time>(
+ mtime + make_timespan(rule_expiration.get_days()*24*60*60 - ceph::real_clock::to_time_t(mtime)%(24*60*60) + 24*60*60));
+ }
+ }
+
+ // update earliest expiration
+ if (rule_expiration_date) {
+ if ((! expiration_date) ||
+ (*expiration_date > *rule_expiration_date)) {
+ expiration_date =
+ boost::optional<ceph::real_time>(rule_expiration_date);
+ rule_id = boost::optional<std::string>(id);
+ }
+ }
+ }
+
+ // cond format header
+ if (expiration_date && rule_id) {
+ // Fri, 23 Dec 2012 00:00:00 GMT
+ char exp_buf[100];
+ time_t exp = ceph::real_clock::to_time_t(*expiration_date);
+ if (std::strftime(exp_buf, sizeof(exp_buf),
+ "%a, %d %b %Y %T %Z", std::gmtime(&exp))) {
+ hdr = fmt::format("expiry-date=\"{0}\", rule-id=\"{1}\"", exp_buf,
+ *rule_id);
+ } else {
+ ldpp_dout(dpp, 0) << __func__ <<
+ "() strftime of life cycle expiration header failed"
+ << dendl;
+ }
+ }
+
+ return hdr;
+
+} /* rgwlc_s3_expiration_header */
+
+} /* namespace rgw::lc */