]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
rgw: add support for noncurrentversion expiration in s3 lifecycle.
authorZhang Shaowen <zhangshaowen@cmss.chinamobile.com>
Mon, 6 Feb 2017 05:45:24 +0000 (13:45 +0800)
committerZhang Shaowen <zhangshaowen@cmss.chinamobile.com>
Fri, 17 Feb 2017 04:43:55 +0000 (12:43 +0800)
Fixes: http://tracker.ceph.com/issues/18916
Signed-off-by: Zhang Shaowen <zhangshaowen@cmss.chinamobile.com>
src/rgw/rgw_lc.cc
src/rgw/rgw_lc.h
src/rgw/rgw_lc_s3.cc
src/rgw/rgw_lc_s3.h

index aac5699c135e1bd6c31ac2d64292c7c2a39cd624..b9770e332a0720151ac32735535830da3ce86b5f 100644 (file)
@@ -36,7 +36,13 @@ bool LCRule::validate()
   if (id.length() > MAX_ID_LEN) {
     return false;
   }
-  else if (expiration.get_days() <= 0) {
+  else if(expiration.empty() && noncur_expiration.empty()) {
+    return false;
+  }
+  else if (!expiration.empty() && expiration.get_days() <= 0) {
+    return false;
+  }
+  else if (!noncur_expiration.empty() && noncur_expiration.get_days() <=0) {
     return false;
   }
   return true;
@@ -47,12 +53,22 @@ void RGWLifecycleConfiguration::add_rule(LCRule *rule)
   string id;
   rule->get_id(id); // not that this will return false for groups, but that's ok, we won't search groups
   rule_map.insert(pair<string, LCRule>(id, *rule));
-  _add_rule(rule);
 }
 
-void RGWLifecycleConfiguration::_add_rule(LCRule *rule)
+bool RGWLifecycleConfiguration::_add_rule(LCRule *rule)
 {
-  prefix_map[rule->get_prefix()] = rule->get_expiration().get_days();
+  lc_op op;
+  if (rule->get_status().compare("Enabled") == 0) {
+    op.status = true;
+  }
+  if (!rule->get_expiration().empty()) {
+    op.expiration = rule->get_expiration().get_days();
+  }
+  if (!rule->get_noncur_expiration().empty()) {
+    op.noncur_expiration = rule->get_noncur_expiration().get_days();
+  }
+  auto ret = prefix_map.insert(pair<string, lc_op>(rule->get_prefix(), op));
+  return ret.second;
 }
 
 int RGWLifecycleConfiguration::check_and_add_rule(LCRule *rule)
@@ -67,9 +83,7 @@ int RGWLifecycleConfiguration::check_and_add_rule(LCRule *rule)
   }
   rule_map.insert(pair<string, LCRule>(id, *rule));
 
-  auto ret = prefix_map.insert(pair<string, int>(rule->get_prefix(), rule->get_expiration().get_days()));
-  //Now prefix shouldn't be the same. When we add noncurrent expiration or other action, prefix may be same.
-  if (!ret.second) {
+  if (!_add_rule(rule)) {
     return -ERR_INVALID_REQUEST;
   }
   return 0;
@@ -82,15 +96,24 @@ bool RGWLifecycleConfiguration::validate()
   if (prefix_map.size() < 2) {
     return true;
   }
-  auto next_iter = prefix_map.begin();
-  auto cur_iter = next_iter++;
-  while (next_iter != prefix_map.end()) {
-    string c_pre = cur_iter->first;
-    string n_pre = next_iter->first;
-    if (n_pre.compare(0, c_pre.length(), c_pre) == 0) {
-      return false;
-    }
+  auto cur_iter = prefix_map.begin();
+  while (cur_iter != prefix_map.end()) {
+    auto next_iter = cur_iter;
     ++next_iter;
+    while (next_iter != prefix_map.end()) {
+      string c_pre = cur_iter->first;
+      string n_pre = next_iter->first;
+      if (n_pre.compare(0, c_pre.length(), c_pre) == 0) {
+        if ((cur_iter->second.expiration > 0 && next_iter->second.expiration > 0) ||
+          (cur_iter->second.noncur_expiration > 0 && next_iter->second.noncur_expiration > 0)) {
+          return false;
+        } else {
+          ++next_iter;
+        }
+      } else {
+        break;
+      }
+    }
     ++cur_iter;
   }
   return true;
@@ -230,6 +253,18 @@ bool RGWLC::obj_has_expired(double timediff, int days)
        return (timediff >= cmp);
 }
 
+int RGWLC::remove_expired_obj(RGWBucketInfo& bucket_info, rgw_obj_key obj_key, bool remove_indeed)
+{
+  if (remove_indeed) {
+    return rgw_remove_object(store, bucket_info, bucket_info.bucket, obj_key);
+  } else {
+    obj_key.instance.clear();
+    RGWObjectCtx rctx(store);
+    rgw_obj obj(bucket_info.bucket, obj_key);
+    return store->delete_obj(rctx, bucket_info, obj, bucket_info.versioning_status());
+  }
+}
+
 int RGWLC::bucket_lc_process(string& shard_id)
 {
   RGWLifecycleConfiguration  config(cct);
@@ -237,8 +272,6 @@ int RGWLC::bucket_lc_process(string& shard_id)
   map<string, bufferlist> bucket_attrs;
   string next_marker, no_ns, list_versions;
   bool is_truncated;
-  bool default_config = false;
-  int default_days = 0;
   vector<RGWObjEnt> objs;
   RGWObjectCtx obj_ctx(store);
   vector<std::string> result;
@@ -273,84 +306,15 @@ int RGWLC::bucket_lc_process(string& shard_id)
       return -1;
     }
 
-  map<string, int>& prefix_map = config.get_prefix_map();
-  for(map<string, int>::iterator prefix_iter = prefix_map.begin(); prefix_iter != prefix_map.end(); ++prefix_iter) {
-    if (prefix_iter->first.empty()) {
-      default_config = true;
-      default_days = prefix_iter->second;
-      break;
-    }
-  }
-
-  if (default_config) {
-    do {
-
-      objs.clear();
-      list_op.params.marker = list_op.get_next_marker();
-      ret = list_op.list_objects(1000, &objs, NULL, &is_truncated);
-      if (ret < 0) {
-        if (ret == -ENOENT)
-          return 0;
-        ldout(cct, 0) << "ERROR: store->list_objects():" <<dendl;
-        return ret;
-      }
-
-      vector<RGWObjEnt>::iterator obj_iter;
-      int pos = 0;
-      utime_t now = ceph_clock_now();
-      for (obj_iter = objs.begin(); obj_iter != objs.end(); ++obj_iter) {
-        bool prefix_match = false;
-        int match_days = 0;
-        map<string, int>& prefix_map = config.get_prefix_map();
-
-        for(map<string, int>::iterator prefix_iter = prefix_map.begin(); prefix_iter != prefix_map.end(); ++prefix_iter) {
-          if (prefix_iter->first.empty()) {
-            continue;
-          }
-          pos = (*obj_iter).key.name.find(prefix_iter->first, 0);
-          if (pos != 0) {
-            continue;
-          }
-          prefix_match = true;
-          match_days = prefix_iter->second;
-          break;
-        }
-        int days = 0;
-        if (prefix_match) {
-          days = match_days;
-        } else if (default_config) {
-          days = default_days;
-        } else {
-          continue;
-        }
-        if (obj_has_expired(now - ceph::real_clock::to_time_t((*obj_iter).mtime), days)) {
-          RGWObjectCtx rctx(store);
-          rgw_obj obj(bucket_info.bucket, (*obj_iter).key.name);
-          RGWObjState *state;
-          int ret = store->get_obj_state(&rctx, obj, &state, false);
-          if (ret < 0) {
-            return ret;
-          }
-          if (state->mtime != (*obj_iter).mtime) //Check mtime again to avoid delete a recently update object as much as possible
-            continue;
-          ret = rgw_remove_object(store, bucket_info, bucket_info.bucket, (*obj_iter).key);
-          if (ret < 0) {
-            ldout(cct, 0) << "ERROR: rgw_remove_object " << dendl;
-          } else {
-            ldout(cct, 10) << "DELETED:" << bucket_name << ":" << (*obj_iter).key.name <<dendl;
-          }
-        }
-      }
-    } while (is_truncated);
-  } else {
-    for(map<string, int>::iterator prefix_iter = prefix_map.begin(); prefix_iter != prefix_map.end(); ++prefix_iter) {
-      if (prefix_iter->first.empty()) {
+  map<string, lc_op>& prefix_map = config.get_prefix_map();
+  list_op.params.list_versions = bucket_info.versioned();
+  if (!bucket_info.versioned()) {
+    for(auto prefix_iter = prefix_map.begin(); prefix_iter != prefix_map.end(); ++prefix_iter) {
+      if (!prefix_iter->second.status || prefix_iter->second.expiration <=0) {
         continue;
       }
       list_op.params.prefix = prefix_iter->first;
-
       do {
-
         objs.clear();
         list_op.params.marker = list_op.get_next_marker();
         ret = list_op.list_objects(1000, &objs, NULL, &is_truncated);
@@ -362,26 +326,110 @@ int RGWLC::bucket_lc_process(string& shard_id)
           return ret;
         }
 
-        vector<RGWObjEnt>::iterator obj_iter;
-        int days = prefix_iter->second;
         utime_t now = ceph_clock_now();
 
-        for (obj_iter = objs.begin(); obj_iter != objs.end(); ++obj_iter) {
-          if (obj_has_expired(now - ceph::real_clock::to_time_t((*obj_iter).mtime), days)) {
+        for (auto obj_iter = objs.begin(); obj_iter != objs.end(); ++obj_iter) {
+          if (obj_has_expired(now - ceph::real_clock::to_time_t(obj_iter->mtime), prefix_iter->second.expiration)) {
             RGWObjectCtx rctx(store);
-            rgw_obj obj(bucket_info.bucket, (*obj_iter).key.name);
+            rgw_obj obj(bucket_info.bucket, obj_iter->key.name);
             RGWObjState *state;
             int ret = store->get_obj_state(&rctx, obj, &state, false);
             if (ret < 0) {
               return ret;
             }
-            if (state->mtime != (*obj_iter).mtime)//Check mtime again to avoid delete a recently update object as much as possible
+            if (state->mtime != obj_iter->mtime)//Check mtime again to avoid delete a recently update object as much as possible
               continue;
-            ret = rgw_remove_object(store, bucket_info, bucket_info.bucket, (*obj_iter).key);
+            ret = remove_expired_obj(bucket_info, obj_iter->key, true);
+            if (ret < 0) {
+              ldout(cct, 0) << "ERROR: remove_expired_obj " << dendl;
+            } else {
+              ldout(cct, 10) << "DELETED:" << bucket_name << ":" << obj_iter->key.name << dendl;
+            }
+          }
+        }
+      } while (is_truncated);
+    }
+  } else {
+  //bucket versioning is enabled or suspended
+    rgw_obj_key pre_marker;
+    for(auto prefix_iter = prefix_map.begin(); prefix_iter != prefix_map.end(); ++prefix_iter) {
+      if (!prefix_iter->second.status) {
+        continue;
+      }
+      if (prefix_iter != prefix_map.begin() && 
+        (prefix_iter->first.compare(0, prev(prefix_iter)->first.length(), prev(prefix_iter)->first) == 0)) {
+        list_op.next_marker = pre_marker;
+      } else {
+        pre_marker = list_op.get_next_marker();
+      }
+      list_op.params.prefix = prefix_iter->first;
+      RGWObjEnt pre_obj;
+      do {
+        if (!objs.empty()) {
+          pre_obj = objs.back();
+        }
+        objs.clear();
+        list_op.params.marker = list_op.get_next_marker();
+        ret = list_op.list_objects(1000, &objs, NULL, &is_truncated);
+
+        if (ret < 0) {
+          if (ret == (-ENOENT))
+            return 0;
+          ldout(cct, 0) << "ERROR: store->list_objects():" <<dendl;
+          return ret;
+        }
+
+        utime_t now = ceph_clock_now();
+        ceph::real_time mtime;
+        bool remove_indeed = true;
+        int expiration;
+        for (auto obj_iter = objs.begin(); obj_iter != objs.end(); ++obj_iter) {
+          if (obj_iter->is_current()) {
+            if (prefix_iter->second.expiration <= 0) {
+              continue;
+            }
+            if (obj_iter->is_delete_marker()) {
+              if ((obj_iter + 1)==objs.end()) {
+                if (is_truncated) {
+                  //deal with it in next round because we can't judge whether this marker is the only version
+                  list_op.next_marker = obj_iter->key;
+                  break;
+                }
+              } else if (obj_iter->key.name.compare((obj_iter + 1)->key.name) == 0) {   //*obj_iter is delete marker and isn't the only version, do nothing.
+                continue;
+              }
+              remove_indeed = true;   //we should remove the delete marker if it's the only version
+            } else {
+              remove_indeed = false;
+            }
+            mtime = obj_iter->mtime;
+            expiration = prefix_iter->second.expiration;
+          } else {
+            if (prefix_iter->second.noncur_expiration <=0) {
+              continue;
+            }
+            remove_indeed = true;
+            mtime = (obj_iter == objs.begin())?pre_obj.mtime:(obj_iter - 1)->mtime;
+            expiration = prefix_iter->second.noncur_expiration;
+          }
+          if (obj_has_expired(now - ceph::real_clock::to_time_t(mtime), expiration)) {
+            if (obj_iter->is_visible()) {
+              RGWObjectCtx rctx(store);
+              rgw_obj obj(bucket_info.bucket, obj_iter->key.name);
+              obj.set_instance(obj_iter->key.instance);
+              RGWObjState *state;
+              int ret = store->get_obj_state(&rctx, obj, &state, false);
+              if (ret < 0) {
+                return ret;
+              }
+              if (state->mtime != obj_iter->mtime)//Check mtime again to avoid delete a recently update object as much as possible
+                continue;
+            }
+            ret = remove_expired_obj(bucket_info, obj_iter->key, remove_indeed);
             if (ret < 0) {
-              ldout(cct, 0) << "ERROR: rgw_remove_object " << dendl;
+              ldout(cct, 0) << "ERROR: remove_expired_obj " << dendl;
             } else {
-              ldout(cct, 10) << "DELETED:" << bucket_name << ":" << (*obj_iter).key.name << dendl;
+              ldout(cct, 10) << "DELETED:" << bucket_name << ":" << obj_iter->key.name << dendl;
             }
           }
         }
index 5436abcdf1a733ace85b508d4adfb8d23d6e6e2b..ac79f7f3237066b7ef6c591a8beaad94e9a37a3f 100644 (file)
@@ -55,6 +55,9 @@ public:
 //  static void generate_test_instances(list<ACLOwner*>& o);
   void set_days(const string& _days) { days = _days; }
   int get_days() {return atoi(days.c_str()); }
+  bool empty() const{
+    return days.empty();
+  }
 };
 WRITE_CLASS_ENCODER(LCExpiration)
 
@@ -65,6 +68,7 @@ protected:
   string prefix;
   string status;
   LCExpiration expiration;
+  LCExpiration noncur_expiration;
 
 public:
 
@@ -88,6 +92,10 @@ public:
     return expiration;
   }
 
+  LCExpiration& get_noncur_expiration() {
+    return noncur_expiration;
+  }
+
   void set_id(string*_id) {
     id = *_id;
   }
@@ -104,35 +112,53 @@ public:
     expiration = *_expiration;
   }
 
+  void set_noncur_expiration(LCExpiration*_noncur_expiration) {
+    noncur_expiration = *_noncur_expiration;
+  }
+
   bool validate();
   
   void encode(bufferlist& bl) const {
-     ENCODE_START(1, 1, bl);
+     ENCODE_START(2, 1, bl);
      ::encode(id, bl);
      ::encode(prefix, bl);
      ::encode(status, bl);
      ::encode(expiration, bl);
+     ::encode(noncur_expiration, bl);
      ENCODE_FINISH(bl);
    }
    void decode(bufferlist::iterator& bl) {
-     DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl);
+     DECODE_START_LEGACY_COMPAT_LEN(2, 1, 1, bl);
      ::decode(id, bl);
      ::decode(prefix, bl);
      ::decode(status, bl);
      ::decode(expiration, bl);
+     if (struct_v >=2) {
+       ::decode(noncur_expiration, bl);
+     }
      DECODE_FINISH(bl);
    }
 
 };
 WRITE_CLASS_ENCODER(LCRule)
 
+struct lc_op
+{
+  bool status;
+  int expiration;
+  int noncur_expiration;
+
+  lc_op() : status(false), expiration(0), noncur_expiration(0) {}
+  
+};
+
 class RGWLifecycleConfiguration
 {
 protected:
   CephContext *cct;
-  map<string, int> prefix_map;
+  map<string, lc_op> prefix_map;
   multimap<string, LCRule> rule_map;
-  void _add_rule(LCRule *rule);
+  bool _add_rule(LCRule *rule);
 public:
   RGWLifecycleConfiguration(CephContext *_cct) : cct(_cct) {}
   RGWLifecycleConfiguration() : cct(NULL) {}
@@ -170,7 +196,7 @@ public:
   bool validate();
 
   multimap<string, LCRule>& get_rule_map() { return rule_map; }
-  map<string, int>& get_prefix_map() { return prefix_map; }
+  map<string, lc_op>& get_prefix_map() { return prefix_map; }
 /*
   void create_default(string id, string name) {
     ACLGrant grant;
@@ -227,6 +253,7 @@ class RGWLC {
   void stop_processor();
 
   private:
+  int remove_expired_obj(RGWBucketInfo& bucket_info, rgw_obj_key obj_key, bool remove_indeed = true);
   bool obj_has_expired(double timediff, int days);
 };
 
index 98695b88c54a02fc0591845e2af9b1a0d1011b8f..2b1e1d29508134eaf9b9b3f20fd98c66da2b3c6c 100644 (file)
@@ -23,6 +23,15 @@ bool LCExpiration_S3::xml_end(const char * el) {
   return true;
 }
 
+bool LCNoncurExpiration_S3::xml_end(const char *el) {
+  LCNoncurDays_S3 *lc_noncur_days = static_cast<LCNoncurDays_S3 *>(find_first("NoncurrentDays"));
+  if (!lc_noncur_days) {
+    return false;
+  }
+  days = lc_noncur_days->get_data();
+  return true;
+}
+
 bool RGWLifecycleConfiguration_S3::xml_end(const char *el) {
   XMLObjIter iter = find("Rule");
   LCRule_S3 *rule = static_cast<LCRule_S3 *>(iter.get_next());
@@ -38,6 +47,7 @@ bool LCRule_S3::xml_end(const char *el) {
   LCPrefix_S3 *lc_prefix;
   LCStatus_S3 *lc_status;
   LCExpiration_S3 *lc_expiration;
+  LCNoncurExpiration_S3 *lc_noncur_expiration;
 
   id.clear();
   prefix.clear();
@@ -61,20 +71,34 @@ bool LCRule_S3::xml_end(const char *el) {
     return false;
 
   lc_expiration = static_cast<LCExpiration_S3 *>(find_first("Expiration"));
-  if (!lc_expiration)
+  lc_noncur_expiration = static_cast<LCNoncurExpiration_S3 *>(find_first("NoncurrentVersionExpiration"));
+  if (!lc_expiration && !lc_noncur_expiration) {
     return false;
-  expiration = *lc_expiration;
+  } else {
+    if (lc_expiration) {
+      expiration = *lc_expiration;
+    }
+    if (lc_noncur_expiration) {
+      noncur_expiration = *lc_noncur_expiration;
+    }
+  }
 
   return true;
 }
 
 void LCRule_S3::to_xml(CephContext *cct, ostream& out) {
-  LCExpiration_S3& expir = static_cast<LCExpiration_S3&>(expiration);
   out << "<Rule>" ;
   out << "<ID>" << id << "</ID>";
   out << "<Prefix>" << prefix << "</Prefix>";
   out << "<Status>" << status << "</Status>";
-  expir.to_xml(out);
+  if (!expiration.empty()) {
+    LCExpiration_S3& expir = static_cast<LCExpiration_S3&>(expiration);
+    expir.to_xml(out);
+  }
+  if (!noncur_expiration.empty()) {
+    LCNoncurExpiration_S3& noncur_expir = static_cast<LCNoncurExpiration_S3&>(noncur_expiration);
+    noncur_expir.to_xml(out);
+  }
   out << "</Rule>";
 }
 
@@ -123,6 +147,10 @@ XMLObj *RGWLCXMLParser_S3::alloc_obj(const char *el)
     obj = new LCExpiration_S3();
   } else if (strcmp(el, "Days") == 0) {
     obj = new LCDays_S3();
+  } else if (strcmp(el, "NoncurrentVersionExpiration") == 0) {
+    obj = new LCNoncurExpiration_S3();
+  } else if (strcmp(el, "NoncurrentDays") == 0) {
+    obj = new LCNoncurDays_S3();
   }
   return obj;
 }
index 01f3b324f9d7a935652f671834df7d2be6a456bd..22f0e4555baffbf24d166bf6959236a692280acb 100644 (file)
@@ -48,6 +48,16 @@ public:
   string& to_str() { return data; }
 };
 
+class LCNoncurDays_S3 : public XMLObj
+{
+public:
+  LCNoncurDays_S3() {}
+  ~LCNoncurDays_S3() {}
+  string& to_str() {
+    return data;
+  }
+};
+
 class LCExpiration_S3 : public LCExpiration, public XMLObj
 {
 public:
@@ -65,6 +75,23 @@ public:
   }
 };
 
+class LCNoncurExpiration_S3 : public LCExpiration, public XMLObj
+{
+public:
+  LCNoncurExpiration_S3() {}
+  ~LCNoncurExpiration_S3() {}
+  
+  bool xml_end(const char *el);
+  void to_xml(ostream& out) {
+    out << "<NoncurrentVersionExpiration>" << "<NoncurrentDays>" << days << "</NoncurrentDays>"<< "</NoncurrentVersionExpiration>";
+  }
+  void dump_xml(Formatter *f) const {
+    f->open_object_section("NoncurrentVersionExpiration");
+    encode_xml("NoncurrentDays", days, f);
+    f->close_section(); 
+  }
+};
+
 class LCRule_S3 : public LCRule, public XMLObj
 {
 public:
@@ -75,14 +102,19 @@ public:
   bool xml_end(const char *el);
   bool xml_start(const char *el, const char **attr);
   void dump_xml(Formatter *f) const {
-         const LCExpiration_S3& expir = static_cast<const LCExpiration_S3&>(expiration);
-
-         f->open_object_section("Rule");
-         encode_xml("ID", id, f);
-         encode_xml("Prefix", prefix, f);
-         encode_xml("Status", status, f);
-         expir.dump_xml(f);
-         f->close_section(); // Rule
+    f->open_object_section("Rule");
+    encode_xml("ID", id, f);
+    encode_xml("Prefix", prefix, f);
+    encode_xml("Status", status, f);
+    if (!expiration.empty()) {
+      const LCExpiration_S3& expir = static_cast<const LCExpiration_S3&>(expiration);
+      expir.dump_xml(f);
+    }
+    if (!noncur_expiration.empty()) {
+      const LCNoncurExpiration_S3& noncur_expir = static_cast<const LCNoncurExpiration_S3&>(noncur_expiration);
+      noncur_expir.dump_xml(f);
+    }
+    f->close_section(); // Rule
   }
 };