diff --git a/doc/man/8/radosgw-admin.rst b/doc/man/8/radosgw-admin.rst index 54a66e17a0b8..e477ccb84f39 100644 --- a/doc/man/8/radosgw-admin.rst +++ b/doc/man/8/radosgw-admin.rst @@ -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. diff --git a/src/rgw/driver/rados/rgw_rados.cc b/src/rgw/driver/rados/rgw_rados.cc index 4f1eea44dc3e..8048f1bcab47 100644 --- a/src/rgw/driver/rados/rgw_rados.cc +++ b/src/rgw/driver/rados/rgw_rados.cc @@ -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& 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& 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::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; } } diff --git a/src/rgw/driver/rados/rgw_rados.h b/src/rgw/driver/rados/rgw_rados.h index 76e5b6c13e19..47b6bc61710f 100644 --- a/src/rgw/driver/rados/rgw_rados.h +++ b/src/rgw/driver/rados/rgw_rados.h @@ -1091,7 +1091,8 @@ class RGWRados 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 @@ class RGWRados 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 >& 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 @@ class RGWRados 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, diff --git a/src/rgw/rgw_admin.cc b/src/rgw/rgw_admin.cc index 15bdaba87a56..7237fe8ed1fe 100644 --- a/src/rgw/rgw_admin.cc +++ b/src/rgw/rgw_admin.cc @@ -7872,7 +7872,7 @@ int main(int argc, const char **argv) auto process = [&](const std::string& p_object, const std::string& p_object_version) -> int { std::unique_ptr 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 @@ int main(int argc, const char **argv) } 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;