]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: enhance functionality of `radosgw-admin object reindex ...`
authorJ. Eric Ivancich <ivancich@redhat.com>
Mon, 12 Jun 2023 17:41:02 +0000 (13:41 -0400)
committerJ. Eric Ivancich <ivancich@redhat.com>
Wed, 9 Aug 2023 14:08:03 +0000 (10:08 -0400)
Adds ability to handle versioned buckets to reindexing. Also, it
completes the addition of the index entries without the subsequent
need to list the bucket.

Signed-off-by: J. Eric Ivancich <ivancich@redhat.com>
doc/man/8/radosgw-admin.rst
src/rgw/driver/rados/rgw_rados.cc
src/rgw/driver/rados/rgw_rados.h
src/rgw/rgw_admin.cc

index 54a66e17a0b8af8e1ff60f32aa3aff22a951a5d5..e477ccb84f394ddc929be28783a3b2f049c3f1b2 100644 (file)
@@ -146,6 +146,9 @@ which are as follows:
 :command:`object rewrite`
   Rewrite the specified object.
 
+:command:`object reindex`
+  Add an object to its bucket's index. Used rarely for emergency repairs.
+
 :command:`objects expire`
   Run expired objects cleanup.
 
index 4f1eea44dc3e863a4b53bc970c777de3d9c9a3ee..8048f1bcab47fcb2a16740e7afc43ffb73757948 100644 (file)
@@ -117,6 +117,32 @@ static string default_storage_extra_pool_suffix = "rgw.buckets.non-ec";
 static RGWObjCategory main_category = RGWObjCategory::Main;
 #define RGW_USAGE_OBJ_PREFIX "usage."
 
+// reads attribute as std::string
+static inline void read_attr(std::map<std::string, bufferlist>& attrs,
+                            const std::string& attr_name,
+                            std::string& dest,
+                            bool* found = nullptr) {
+  auto i = attrs.find(attr_name);
+  if (i != attrs.end()) {
+    dest = rgw_bl_str(i->second);
+  }
+  if (found) *found = i != attrs.end();
+}
+
+// reads attribute as bufferlist
+static inline void read_attr(std::map<std::string, bufferlist>& attrs,
+                            const std::string& attr_name,
+                            bufferlist& dest,
+                            bool* found = nullptr) {
+  auto i = attrs.find(attr_name);
+  if (i != attrs.cend()) {
+    dest = i->second; // copy
+  }
+  if (found) {
+    *found = i != attrs.end();
+  }
+}
+
 rgw_raw_obj rgw_obj_select::get_raw_obj(RGWRados* store) const
 {
   if (!is_raw) {
@@ -3620,33 +3646,235 @@ int RGWRados::rewrite_obj(RGWBucketInfo& dest_bucket_info, const rgw_obj& obj, c
                        attrset, 0, real_time(), NULL, dpp, y);
 }
 
-int RGWRados::reindex_obj(const RGWBucketInfo& bucket_info,
-                         const rgw_obj& obj,
+
+int RGWRados::reindex_obj(rgw::sal::Driver* driver,
+                         RGWBucketInfo& bucket_info,
+                         const rgw_obj& head_obj,
                          const DoutPrefixProvider* dpp,
                          optional_yield y)
 {
-  if (bucket_info.versioned()) {
-    ldpp_dout(dpp, 10) << "WARNING: " << __func__ <<
-      ": cannot process versioned bucket \"" <<
-      bucket_info.bucket.get_key() << "\"" <<
-      dendl;
-    return -ENOTSUP;
+  // used for trimming pending entries; max value means all versions trimmed
+  const uint64_t max_ver = std::numeric_limits<uint64_t>::max();
+  // used for linking an olh
+  const std::string empty_op_tag = "";
+
+  int ret;
+  RGWObjectCtx obj_ctx(driver);
+
+  // aids in printing out name of bucket/object
+  auto p = [](const rgw_obj& o) -> std::string {
+    std::stringstream ss;
+    ss << o.bucket.name << ':' << o.key;
+    return ss.str();
+  };
+
+  // since the code for linking a versioned object and adding a delete
+  // marker is so similar, we bring the common OLH-handling code into
+  // this lambda
+  auto link_helper = [&](const bool is_delete_marker,
+                        rgw_bucket_dir_entry_meta& meta,
+                        const std::string& log_tag) -> int {
+    int ret = 0;
+
+    // convert the head object name into the OLH object by removing
+    // the instance info
+    rgw_obj olh_obj = head_obj;
+    olh_obj.key.instance.clear();
+
+    RGWObjState* olh_state { nullptr };
+    RGWObjManifest* olh_manifest { nullptr }; // we don't use, but must send in
+    ret = get_obj_state(dpp, &obj_ctx, bucket_info, olh_obj,
+                       &olh_state, &olh_manifest,
+                       false, // don't follow olh
+                       y);
+    if (ret < 0) {
+      ldpp_dout(dpp, 0) << "ERROR: " << __func__ <<
+       ": during " << log_tag << " get_obj_state on OLH object " <<
+       olh_obj.key << " returned: " << cpp_strerror(-ret) << dendl;
+      return ret;
+    }
+
+    // In order to update the data in the OLH object we're calling
+    // bucket_index_link_olh followed by bucket_index_trim_olh_log
+    // since that churns metadata less than a call to set_olh
+    // would. bucket_index_link_olh does leave entries in the OLH
+    // object's pending log since normally OLH updates are paired with
+    // other ops, but we remove such entries below.
+    ret = bucket_index_link_olh(dpp,
+                               bucket_info,
+                               *olh_state,
+                               head_obj,
+                               is_delete_marker,
+                               empty_op_tag,
+                               &meta,
+                               0, // zero olh_epoch means calculated in CLS
+                               ceph::real_clock::zero(), // unmod_since
+                               true, // high_precision_time
+                               y,
+                               nullptr, // zones trace
+                               false); // log data change
+    if (ret < 0) {
+      ldpp_dout(dpp, 0) << "ERROR: " << __func__ <<
+       ": during " << log_tag << " set_index_link_olh returned: " <<
+       cpp_strerror(-ret) << dendl;
+      return ret;
+    }
+
+    // bucket_)index_link_olh leaves a pending_log entry in the OLH;
+    // this trims it out
+    ret = bucket_index_trim_olh_log(dpp,
+                                   bucket_info,
+                                   *olh_state,
+                                   head_obj,
+                                   max_ver,
+                                   y);
+    if (ret < 0) {
+      ldpp_dout(dpp, 0) << "ERROR: " << __func__ <<
+       ": during " << log_tag <<
+       " bucket_index_trim_olh_log returned: " <<
+       cpp_strerror(-ret) << dendl;
+      return ret;
+    }
+
+    return 0;
+  }; // link_helper lambda
+
+  librados::IoCtx head_obj_ctx;
+  ret = get_obj_head_ioctx(dpp, bucket_info, head_obj, &head_obj_ctx);
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: " << __func__ <<
+      ": get_obj_head_ioctx for " << p(head_obj) << " returned: " <<
+      cpp_strerror(-ret) << dendl;
+    return ret;
   }
 
-  Bucket target(this, bucket_info);
-  RGWRados::Bucket::UpdateIndex update_idx(&target, obj);
-  const std::string* no_write_tag = nullptr;
+  const int64_t pool_id = head_obj_ctx.get_id();
+  const bool is_versioned = bucket_info.versioned();
+  const bool has_instance = ! head_obj.key.instance.empty();
+
+  ldpp_dout(dpp, 20) << "INFO: " << __func__ << ": reindexing " <<
+    p(head_obj) << dendl;
+
+  RGWObjState *head_state { nullptr };
+  RGWObjManifest *head_manifest { nullptr };
 
-  int ret = update_idx.prepare(dpp, RGWModifyOp::CLS_RGW_OP_ADD, no_write_tag, y);
+  // if head_obj does not exist does not return -ENOENT but instead
+  // sets head_state->exists to false
+  ret = get_obj_state(dpp, &obj_ctx, bucket_info, head_obj,
+                     &head_state, &head_manifest,
+                     false, // don't follow olh
+                     y);
   if (ret < 0) {
     ldpp_dout(dpp, 0) << "ERROR: " << __func__ <<
-      ": update index prepare for \"" << obj << "\" returned: " <<
+      ": get_obj_state on " << p(head_obj) << " returned: " <<
       cpp_strerror(-ret) << dendl;
     return ret;
   }
 
-  return 0;
-}
+  if (! head_state->exists && is_versioned && has_instance) {
+    // head object does not exist if it's a delete marker; handle here
+    // and return
+    ldpp_dout(dpp, 20) << "INFO: " << __func__ << ": indexing " <<
+      p(head_obj) << " as delete marker" << dendl;
+
+    // empty metadata object is fine for delete marker
+    rgw_bucket_dir_entry_meta meta;
+
+    return link_helper(true, meta, "set delete marker");
+  } else if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: " << __func__ <<
+      ": unable to complete stat of " << p(head_obj) << "; returned: " <<
+      cpp_strerror(-ret) << dendl;
+    return ret;
+  }
+
+  // data we'll pull from head object xattrs
+  std::string etag;
+  std::string content_type;
+  std::string storage_class;
+  bufferlist acl_bl;
+  bool found_olh_info { false };
+  bufferlist olh_info_bl;
+  bool appendable { false };
+  bufferlist part_num_bl;
+
+  rgw::sal::Attrs& attr_set = head_state->attrset;
+  read_attr(attr_set, RGW_ATTR_ETAG, etag);
+  read_attr(attr_set, RGW_ATTR_CONTENT_TYPE, content_type);
+  read_attr(attr_set, RGW_ATTR_STORAGE_CLASS, storage_class);
+  read_attr(attr_set, RGW_ATTR_ACL, acl_bl);
+  read_attr(attr_set, RGW_ATTR_OLH_INFO, olh_info_bl, &found_olh_info);
+  read_attr(attr_set, RGW_ATTR_APPEND_PART_NUM, part_num_bl, &appendable);
+
+  // check for a pure OLH object and if so exit early
+  if (found_olh_info) {
+    try {
+      auto iter = olh_info_bl.cbegin();
+      RGWOLHInfo info;
+      decode(info, iter);
+      if (! info.target.key.instance.empty()) {
+       // since there is a listed instance this appears to be a pure
+       // OLH (i.e., no data); we won't index as we index actual
+       // objects with data and set the OLH then
+       ldpp_dout(dpp, 20) << "INFO: " << __func__ << ": " <<
+         p(head_obj) << " appears to be a pure OLH object; ignoring" << dendl;
+       return 0;
+      }
+    } catch (buffer::error& err) {
+      ldpp_dout(dpp, 0) << "ERROR: " << __func__ <<
+       ": unable to decode OLH info for " << p(head_obj) << dendl;
+      return -EIO;
+    }
+  }
+
+  Bucket bkt(this, bucket_info);
+  RGWRados::Bucket::UpdateIndex update_idx(&bkt, head_obj);
+
+  // note: we can skip calling prepare() since there's no transaction
+  // and we don't specify a write tag (i.e., transaction tag)
+  ret = update_idx.complete(dpp,
+                           pool_id,
+                           0, // bucket index epoch
+                           head_state->size,
+                           head_state->accounted_size,
+                           head_state->mtime,
+                           etag,
+                           content_type,
+                           storage_class,
+                           &acl_bl,
+                           RGWObjCategory::Main, // RGWObjCategory category,
+                           nullptr, // remove_objs list
+                           y,
+                           nullptr, // user data string
+                           appendable);
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: " << __func__ <<
+      ": update index complete for " << p(head_obj) << " returned: " <<
+      cpp_strerror(-ret) << dendl;
+    return ret;
+  }
+
+  if (bucket_info.versioned()) {
+    ldpp_dout(dpp, 20) << "INFO: " << __func__ << ": since " <<
+      bucket_info.bucket << " appears to be versioned, setting OLH for " <<
+      p(head_obj) << dendl;
+
+    // write OLH and instance entries
+    rgw_bucket_dir_entry_meta meta;
+    meta.category = RGWObjCategory::Main;
+    meta.mtime = head_state->mtime;
+    meta.size = head_state->size;
+    meta.accounted_size = head_state->accounted_size;
+    meta.etag = etag;
+    meta.content_type = content_type;
+    meta.appendable = appendable;
+
+    ret = link_helper(false, meta, "linking version");
+  } // if bucket is versioned
+
+  return ret;
+} // RGWRados::reindex_obj
+
 
 struct obj_time_weight {
   real_time mtime;
@@ -7487,7 +7715,7 @@ int RGWRados::repair_olh(const DoutPrefixProvider *dpp, RGWObjState* state, cons
     return r;
   }
   return 0;
-}
+} // RGWRados::repair_olh
 
 int RGWRados::bucket_index_trim_olh_log(const DoutPrefixProvider *dpp,
                                         RGWBucketInfo& bucket_info,
@@ -7703,7 +7931,7 @@ int RGWRados::apply_olh_log(const DoutPrefixProvider *dpp,
   /* update olh object */
   r = rgw_rados_operate(dpp, ref.pool.ioctx(), ref.obj.oid, &op, y);
   if (r < 0) {
-    ldpp_dout(dpp, 0) << "ERROR: could not apply olh update, r=" << r << dendl;
+    ldpp_dout(dpp, 0) << "ERROR: " << __func__ << ": could not apply olh update to oid \"" << ref.obj.oid << "\", r=" << r << dendl;
     return r;
   }
 
@@ -7822,7 +8050,8 @@ int RGWRados::set_olh(const DoutPrefixProvider *dpp, RGWObjectCtx& obj_ctx,
                      const rgw_obj& target_obj, bool delete_marker,
                      rgw_bucket_dir_entry_meta *meta,
                       uint64_t olh_epoch, real_time unmod_since, bool high_precision_time,
-                      optional_yield y, rgw_zone_set *zones_trace, bool log_data_change)
+                      optional_yield y, rgw_zone_set *zones_trace, bool log_data_change,
+                     bool skip_olh_obj_update)
 {
   string op_tag;
 
@@ -7884,6 +8113,11 @@ int RGWRados::set_olh(const DoutPrefixProvider *dpp, RGWObjectCtx& obj_ctx,
     return -EIO;
   }
 
+  // exit early if we're skipping the olh update and just updating the index
+  if (skip_olh_obj_update) {
+    return 0;
+  }
+
   ret = update_olh(dpp, obj_ctx, state, bucket_info, olh_obj, y);
   if (ret == -ECANCELED) { /* already did what we needed, no need to retry, raced with another user */
     ret = 0;
@@ -8053,7 +8287,7 @@ int RGWRados::remove_olh_pending_entries(const DoutPrefixProvider *dpp, const RG
       return 0;
     }
     if (r < 0) {
-      ldpp_dout(dpp, 0) << "ERROR: could not apply olh update, r=" << r << dendl;
+      ldpp_dout(dpp, 0) << "ERROR: " << __func__ << ": could not apply olh update to oid \"" << ref.obj.oid << "\", r=" << r << dendl;
       return r;
     }
   }
index 76e5b6c13e19bb45226bc84c3356eb8d386c1b34..47b6bc61710f22c6be8e5993b15c45c74a971e3e 100644 (file)
@@ -1091,7 +1091,8 @@ public:
   D3nDataCache* d3n_data_cache{nullptr};
 
   int rewrite_obj(RGWBucketInfo& dest_bucket_info, const rgw_obj& obj, const DoutPrefixProvider *dpp, optional_yield y);
-  int reindex_obj(const RGWBucketInfo& dest_bucket_info,
+  int reindex_obj(rgw::sal::Driver* driver,
+                 RGWBucketInfo& dest_bucket_info,
                  const rgw_obj& obj,
                  const DoutPrefixProvider* dpp,
                  optional_yield y);
@@ -1339,7 +1340,8 @@ public:
   int apply_olh_log(const DoutPrefixProvider *dpp, RGWObjectCtx& obj_ctx, RGWObjState& obj_state, RGWBucketInfo& bucket_info, const rgw_obj& obj,
                     bufferlist& obj_tag, std::map<uint64_t, std::vector<rgw_bucket_olh_log_entry> >& log,
                     uint64_t *plast_ver, optional_yield y, rgw_zone_set *zones_trace = nullptr);
-  int update_olh(const DoutPrefixProvider *dpp, RGWObjectCtx& obj_ctx, RGWObjState *state, RGWBucketInfo& bucket_info, const rgw_obj& obj, optional_yield y, rgw_zone_set *zones_trace = nullptr);
+  int update_olh(const DoutPrefixProvider *dpp, RGWObjectCtx& obj_ctx, RGWObjState *state, RGWBucketInfo& bucket_info, const rgw_obj& obj, optional_yield y,
+                rgw_zone_set *zones_trace = nullptr);
   int clear_olh(const DoutPrefixProvider *dpp,
                 RGWObjectCtx& obj_ctx,
                 const rgw_obj& obj,
@@ -1347,9 +1349,19 @@ public:
                 const std::string& tag,
                 const uint64_t ver,
                 optional_yield y);
-  int set_olh(const DoutPrefixProvider *dpp, RGWObjectCtx& obj_ctx, RGWBucketInfo& bucket_info, const rgw_obj& target_obj, bool delete_marker, rgw_bucket_dir_entry_meta *meta,
-              uint64_t olh_epoch, ceph::real_time unmod_since, bool high_precision_time,
-              optional_yield y, rgw_zone_set *zones_trace = nullptr, bool log_data_change = false);
+  int set_olh(const DoutPrefixProvider *dpp,
+             RGWObjectCtx& obj_ctx,
+             RGWBucketInfo& bucket_info,
+             const rgw_obj& target_obj,
+             bool delete_marker,
+             rgw_bucket_dir_entry_meta *meta,
+              uint64_t olh_epoch,
+             ceph::real_time unmod_since,
+             bool high_precision_time,
+              optional_yield y,
+             rgw_zone_set *zones_trace = nullptr,
+             bool log_data_change = false,
+             bool skip_olh_obj_update = false); // can skip the OLH object update if, for example, repairing index
   int repair_olh(const DoutPrefixProvider *dpp, RGWObjState* state, const RGWBucketInfo& bucket_info,
                  const rgw_obj& obj, optional_yield y);
   int unlink_obj_instance(const DoutPrefixProvider *dpp, RGWObjectCtx& obj_ctx, RGWBucketInfo& bucket_info, const rgw_obj& target_obj,
index 15bdaba87a56e0b201ad24f113bc6725f2f1b450..7237fe8ed1fe7526492d7b45d182549b3a921b05 100644 (file)
@@ -7872,7 +7872,7 @@ next:
     auto process = [&](const std::string& p_object, const std::string& p_object_version) -> int {
       std::unique_ptr<rgw::sal::Object> obj = bucket->get_object(p_object);
       obj->set_instance(p_object_version);
-      ret = store->reindex_obj(bucket->get_info(), obj->get_obj(), dpp(), null_yield);
+      ret = store->reindex_obj(driver, bucket->get_info(), obj->get_obj(), dpp(), null_yield);
       if (ret < 0) {
        return ret;
       }
@@ -7894,9 +7894,15 @@ next:
       }
 
       std::string obj_name;
-      const std::string empty_version;
       while (std::getline(file, obj_name)) {
-       ret = process(obj_name, empty_version);
+       std::string version;
+       auto pos = obj_name.find('\t');
+       if (pos != std::string::npos) {
+         version = obj_name.substr(1 + pos);
+         obj_name = obj_name.substr(0, pos);
+       }
+
+       ret = process(obj_name, version);
        if (ret < 0) {
          std::cerr << "ERROR: while processing \"" << obj_name <<
            "\", received " << cpp_strerror(-ret) << "." << std::endl;