]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/rgw/rgw_lc.cc
import 15.2.0 Octopus source
[ceph.git] / ceph / src / rgw / rgw_lc.cc
index 951244396ac0872bc67041efbe8689da6ae6d03c..a38169655a433b21dfdaf63e49ed81edc9e1bc40 100644 (file)
@@ -1,5 +1,5 @@
 // -*- 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>
@@ -7,6 +7,7 @@
 
 #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
@@ -141,6 +151,9 @@ int RGWLifecycleConfiguration::check_and_add_rule(const LCRule& rule)
   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)) {
@@ -201,15 +214,14 @@ void *RGWLC::LCWorker::entry() {
 
     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;
@@ -268,13 +280,13 @@ int RGWLC::bucket_lc_prepare(int index)
 
 #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;
@@ -322,7 +334,7 @@ static bool pass_object_lock_check(RGWRados *store, RGWBucketInfo& bucket_info,
   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;
@@ -386,7 +398,7 @@ int RGWLC::handle_multipart_expiration(
     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;
@@ -422,7 +434,7 @@ static int read_obj_tags(RGWRados *store, RGWBucketInfo& bucket_info, rgw_obj& o
   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)
@@ -439,24 +451,25 @@ 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;
@@ -469,9 +482,9 @@ class LCObjsLister {
   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");
@@ -487,7 +500,7 @@ public:
   }
 
   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;
     }
@@ -503,23 +516,22 @@ public:
 
   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() {
@@ -545,12 +557,12 @@ public:
 
 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) {}
 };
 
@@ -561,17 +573,18 @@ struct lc_op_ctx {
   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)
@@ -593,7 +606,7 @@ 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;
@@ -601,7 +614,7 @@ static int remove_expired_obj(lc_op_ctx& oc, bool remove_indeed)
   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 {
@@ -652,7 +665,7 @@ public:
   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)
@@ -663,7 +676,7 @@ 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;
@@ -680,7 +693,7 @@ static int check_tags(lc_op_ctx& oc, bool *skip)
     }
 
     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;
     }
   }
@@ -756,7 +769,9 @@ public:
       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;
@@ -778,14 +793,16 @@ public:
     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;
@@ -816,7 +833,9 @@ public:
     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;
@@ -847,7 +866,7 @@ public:
 
     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;
@@ -876,20 +895,23 @@ public:
     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;
   }
 };
@@ -948,9 +970,9 @@ void LCOpRule::build()
   }
 }
 
-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;
@@ -987,16 +1009,18 @@ int LCOpRule::process(rgw_bucket_dir_entry& o)
     }
 
     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;
@@ -1010,13 +1034,12 @@ int RGWLC::bucket_lc_process(string& shard_id)
   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;
@@ -1029,7 +1052,7 @@ int RGWLC::bucket_lc_process(string& shard_id)
     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())
@@ -1082,11 +1105,10 @@ int RGWLC::bucket_lc_process(string& shard_id)
 
     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
@@ -1113,7 +1135,7 @@ int RGWLC::bucket_lc_post(int index, int max_lock_sec, pair<string, int >& entry
   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;
@@ -1124,7 +1146,7 @@ int RGWLC::bucket_lc_post(int index, int max_lock_sec, pair<string, int >& entry
       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;
@@ -1136,13 +1158,13 @@ int RGWLC::bucket_lc_post(int index, int max_lock_sec, pair<string, int >& entry
       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);
@@ -1154,7 +1176,7 @@ int RGWLC::list_lc_progress(const string& marker, uint32_t max_entries, map<stri
   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="
@@ -1200,7 +1222,7 @@ int RGWLC::process(int index, int max_lock_secs)
     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;
@@ -1211,7 +1233,7 @@ int RGWLC::process(int index, int max_lock_secs)
       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;
@@ -1229,7 +1251,7 @@ int RGWLC::process(int index, int max_lock_secs)
       }
     }
 
-    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;
@@ -1240,7 +1262,7 @@ int RGWLC::process(int index, int max_lock_secs)
       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;
@@ -1248,18 +1270,18 @@ int RGWLC::process(int index, int max_lock_secs)
     }
 
     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;
 }
 
@@ -1293,8 +1315,8 @@ std::ostream& RGWLC::gen_prefix(std::ostream& out) const
 
 void RGWLC::LCWorker::stop()
 {
-  Mutex::Locker l(lock);
-  cond.Signal();
+  std::lock_guard l{lock};
+  cond.notify_all();
 }
 
 bool RGWLC::going_down()
@@ -1379,7 +1401,7 @@ static std::string get_lc_shard_name(const rgw_bucket& bucket){
 }
 
 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);
@@ -1395,7 +1417,7 @@ static int guard_lc_modify(RGWRados* store, const rgw_bucket& bucket, const stri
   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 {
@@ -1432,7 +1454,9 @@ int RGWLC::set_bucket_config(RGWBucketInfo& bucket_info,
 
   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;
 
@@ -1452,8 +1476,9 @@ int RGWLC::remove_bucket_config(RGWBucketInfo& bucket_info,
 {
   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;
 
@@ -1474,7 +1499,7 @@ int RGWLC::remove_bucket_config(RGWBucketInfo& bucket_info,
 
 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);
@@ -1492,7 +1517,7 @@ int fix_lc_shard_entry(RGWRados* store, const RGWBucketInfo& bucket_info,
   // 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) {
@@ -1520,4 +1545,150 @@ int fix_lc_shard_entry(RGWRados* store, const RGWBucketInfo& bucket_info,
   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 */