From 9fa3433a99a3463b2f71040c4bd6d3341f779813 Mon Sep 17 00:00:00 2001 From: Soumya Koduri Date: Fri, 1 Aug 2025 00:49:44 +0530 Subject: [PATCH] rgw/restore: Update expiry-date of restored copies As per AWS spec (https://docs.aws.amazon.com/AmazonS3/latest/API/API_RestoreObject.html), if a `restore-object` request is re-issued on already restored copy, server needs to update restoration period relative to the current time. These changes handles the same. Note: this applies to only temporary restored copies Signed-off-by: Soumya Koduri --- doc/radosgw/cloud-restore.rst | 3 + src/rgw/driver/rados/rgw_rados.cc | 30 +++++++--- src/rgw/driver/rados/rgw_sal_rados.cc | 18 +++--- src/rgw/rgw_op.cc | 10 ++++ src/rgw/rgw_restore.cc | 79 ++++++++++++++++++++++++++- src/rgw/rgw_restore.h | 9 +++ 6 files changed, 130 insertions(+), 19 deletions(-) diff --git a/doc/radosgw/cloud-restore.rst b/doc/radosgw/cloud-restore.rst index 3afe0c50a4f0..6280720a2d45 100644 --- a/doc/radosgw/cloud-restore.rst +++ b/doc/radosgw/cloud-restore.rst @@ -197,6 +197,9 @@ Example 1: This will restore the object ``doc1.rtf`` at an optional version, for the duration of 10 days. +.. note:: The restoration period of these temporary copies can be updated by reissuing the request with a new period. + + Example 2: .. prompt:: bash $ diff --git a/src/rgw/driver/rados/rgw_rados.cc b/src/rgw/driver/rados/rgw_rados.cc index cfd897aabc01..02dfb31947c7 100644 --- a/src/rgw/driver/rados/rgw_rados.cc +++ b/src/rgw/driver/rados/rgw_rados.cc @@ -5571,22 +5571,17 @@ int RGWRados::restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx, real_time delete_at = real_time(); if (days) { //temp copy; do not change mtime and set expiry date - int expiry_days = days.value(); - constexpr int32_t secs_in_a_day = 24 * 60 * 60; ceph::real_time expiration_date ; + (void)restore->get_expiration_date(dpp, days.value(), expiration_date); - if (cct->_conf->rgw_restore_debug_interval > 0) { - expiration_date = restore_time + make_timespan(double(expiry_days)*cct->_conf->rgw_restore_debug_interval); - ldpp_dout(dpp, 20) << "Setting expiration time to rgw_restore_debug_interval: " << double(expiry_days)*cct->_conf->rgw_restore_debug_interval << ", days:" << expiry_days << dendl; - } else { - expiration_date = restore_time + make_timespan(double(expiry_days) * secs_in_a_day); - } delete_at = expiration_date; + ldpp_dout(dpp, 5) << "Setting Restore expiration time to: " << expiration_date << " , restore_time: " << restore_time << ", restore_interval: " << cct->_conf->rgw_restore_debug_interval << dendl; { bufferlist bl; encode(expiration_date, bl); attrs[RGW_ATTR_RESTORE_EXPIRY_DATE] = std::move(bl); + attrs[RGW_ATTR_DELETE_AT] = attrs[RGW_ATTR_RESTORE_EXPIRY_DATE]; } { bufferlist bl; @@ -7449,9 +7444,10 @@ int RGWRados::set_attrs(const DoutPrefixProvider *dpp, RGWObjectCtx* octx, RGWBu int64_t poolid = ioctx.get_id(); // Retain Object category as CloudTiered while restore is in - // progress or failed + // progress or failed or if its temporarily restored copy RGWObjCategory category = RGWObjCategory::Main; auto r_iter = attrs.find(RGW_ATTR_RESTORE_STATUS); + auto t_iter = attrs.find(RGW_ATTR_RESTORE_TYPE); if (r_iter != attrs.end()) { rgw::sal::RGWRestoreStatus st = rgw::sal::RGWRestoreStatus::None; auto iter = r_iter->second.cbegin(); @@ -7462,10 +7458,26 @@ int RGWRados::set_attrs(const DoutPrefixProvider *dpp, RGWObjectCtx* octx, RGWBu if (st != rgw::sal::RGWRestoreStatus::CloudRestored) { category = RGWObjCategory::CloudTiered; + } else { // check if its temporary copy + if (t_iter != attrs.end()) { + rgw::sal::RGWRestoreType rt; + decode(rt, t_iter->second); + + if (rt == rgw::sal::RGWRestoreType::Temporary) { + category = RGWObjCategory::CloudTiered; + // temporary restore; set storage-class to cloudtier storage class + auto c_iter = attrs.find(RGW_ATTR_CLOUDTIER_STORAGE_CLASS); + + if (c_iter != attrs.end()) { + storage_class = rgw_bl_str(c_iter->second); + } + } + } } } catch (buffer::error& err) { } } + ldpp_dout(dpp, 20) << "Setting obj category:" << category << ", storage_class:" << storage_class << dendl; r = index_op.complete(dpp, poolid, epoch, state->size, state->accounted_size, mtime, etag, content_type, storage_class, owner, category, nullptr, y, nullptr, false, log_op); diff --git a/src/rgw/driver/rados/rgw_sal_rados.cc b/src/rgw/driver/rados/rgw_sal_rados.cc index 5c19e9223a71..fe8094316fba 100644 --- a/src/rgw/driver/rados/rgw_sal_rados.cc +++ b/src/rgw/driver/rados/rgw_sal_rados.cc @@ -3228,13 +3228,10 @@ int RadosObject::handle_obj_expiry(const DoutPrefixProvider* dpp, optional_yield obj_key.instance = "null"; } - real_time read_mtime; - std::unique_ptr read_op(get_read_op()); - read_op->params.lastmod = &read_mtime; - ret = read_op->prepare(y, dpp); + ret = get_obj_attrs(y, dpp); if (ret < 0) { ldpp_dout(dpp, -1) << "handle_obj_expiry Obj:" << get_key() << - ", read_op failed ret=" << ret << dendl; + ", getting object attrs failed ret=" << ret << dendl; return ret; } @@ -3242,6 +3239,11 @@ int RadosObject::handle_obj_expiry(const DoutPrefixProvider* dpp, optional_yield obj_key.instance.clear(); } + // ensure object is not overwritten and is really expired + if (!is_expired()) { + return 0; + } + set_atomic(true); map attrs = get_attrs(); RGWRados::Object op_target(store->getRados(), bucket->get_info(), *rados_ctx, get_obj()); @@ -3273,7 +3275,7 @@ int RadosObject::handle_obj_expiry(const DoutPrefixProvider* dpp, optional_yield obj_op.meta.if_nomatch = NULL; obj_op.meta.user_data = NULL; obj_op.meta.zones_trace = NULL; - obj_op.meta.set_mtime = read_mtime; + obj_op.meta.set_mtime = state.mtime; RGWObjManifest *pmanifest; pmanifest = &m; @@ -3304,6 +3306,7 @@ int RadosObject::handle_obj_expiry(const DoutPrefixProvider* dpp, optional_yield attrs.erase(RGW_ATTR_RESTORE_EXPIRY_DATE); attrs.erase(RGW_ATTR_CLOUDTIER_STORAGE_CLASS); attrs.erase(RGW_ATTR_RESTORE_VERSIONED_EPOCH); + attrs.erase(RGW_ATTR_DELETE_AT); bufferlist bl; bl.append(tier_config.name); @@ -3325,12 +3328,9 @@ int RadosObject::handle_obj_expiry(const DoutPrefixProvider* dpp, optional_yield } } // object is not restored/temporary; go for regular deletion - // ensure object is not overwritten and is really expired - if (is_expired()) { ldpp_dout(dpp, 10) << "Deleting expired obj:" << get_key() << dendl; ret = obj->delete_object(dpp, null_yield, rgw::sal::FLAG_LOG_OP, nullptr, nullptr); - } return ret; } diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index 52e1b8f2d1de..421c266b6d15 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -1047,6 +1047,16 @@ int handle_cloudtier_obj(req_state* s, const DoutPrefixProvider *dpp, rgw::sal:: } } else if (restore_status == rgw::sal::RGWRestoreStatus::CloudRestored) { // corresponds to CLOUD_RESTORED + if (!read_through) { //update expiry date iff its temp restored copy + op_ret = driver->get_rgwrestore()->update_cloud_restore_exp_date(s->bucket.get(), + s->object.get(), days, dpp, y); + + if (op_ret < 0) { + ldpp_dout(dpp, 20) << "Updating expiry-date of restored object " << s->object->get_key() << " failed - " << op_ret << dendl; + s->err.message = "failed to update expiry-date of the restored object"; + return op_ret; + } + } return static_cast(rgw::sal::RGWRestoreStatus::CloudRestored); } else { // first time restore or previous restore failed. // Restore the object. diff --git a/src/rgw/rgw_restore.cc b/src/rgw/rgw_restore.cc index 5f4e707c4799..e29637a72b9e 100644 --- a/src/rgw/rgw_restore.cc +++ b/src/rgw/rgw_restore.cc @@ -439,7 +439,6 @@ int Restore::process_restore_entry(RestoreEntry& entry, optional_yield y) decode(restore_status, iter); } if (restore_status != rgw::sal::RGWRestoreStatus::RestoreAlreadyInProgress) { - // XXX: Check if expiry-date needs to be update ldpp_dout(this, 5) << __PRETTY_FUNCTION__ << ": Restore of object " << obj->get_key() << " not in progress state" << dendl; @@ -526,6 +525,84 @@ int Restore::set_cloud_restore_status(const DoutPrefixProvider* dpp, return ret; } +void Restore::get_expiration_date(const DoutPrefixProvider* dpp, + int expiry_days, ceph::real_time& exp_date) { + constexpr int32_t secs_in_a_day = 24 * 60 * 60; + ceph::real_time cur_time = real_clock::now(); + + ldpp_dout(dpp, 5) << "Calculating expiration date for days:" << expiry_days << dendl; + if (cct->_conf->rgw_restore_debug_interval > 0) { + exp_date = cur_time + make_timespan(double(expiry_days)*cct->_conf->rgw_restore_debug_interval); + } else { + exp_date = cur_time + make_timespan(double(expiry_days) * secs_in_a_day); + } + ldpp_dout(dpp, 5) << "expiration date: " << exp_date << " , cur_time: " << cur_time << ", restore_interval: " << cct->_conf->rgw_restore_debug_interval << dendl; +} + +/* + * As per AWS spec (https://docs.aws.amazon.com/AmazonS3/latest/API/API_RestoreObject.html), + * After restoring an archived object, you can update the restoration period by reissuing the + * request with a new period. Amazon S3 updates the restoration period relative to the current time. + * You cannot update the restoration period when Amazon S3 is actively processing your current restore + * request for the object. + */ +int Restore::update_cloud_restore_exp_date(rgw::sal::Bucket* pbucket, + rgw::sal::Object* pobj, + std::optional days, + const DoutPrefixProvider* dpp, + optional_yield y) +{ + int ret = -1; + ceph::real_time cur_time = real_clock::now(); + + if (!pobj) + return ret; + + if (!days) {// days should be present as we should update + // expiry date only for temp copies + return ret; + } + + ret = pobj->get_obj_attrs(y, dpp); + if (ret < 0) { + ldpp_dout(dpp, 0) << "ERROR: failed to read object attrs " << cpp_strerror(-ret) << dendl; + return ret; + } + + ceph::real_time expiration_date; + get_expiration_date(dpp, days.value(), expiration_date); + + auto& attrs = pobj->get_attrs(); + { + bufferlist bl; + using ceph::encode; + encode(expiration_date, bl); + attrs[RGW_ATTR_RESTORE_EXPIRY_DATE] = attrs[RGW_ATTR_DELETE_AT] = bl; + } + { + bufferlist bl; + using ceph::encode; + encode(cur_time, bl); + attrs[RGW_ATTR_INTERNAL_MTIME] = bl; + } + + pobj->set_atomic(true); + + + ret = pobj->set_obj_attrs(dpp, &attrs, nullptr, y, 0); + + if (ret < 0) { + ldpp_dout(dpp, 0) << "ERROR: failed to update restore expiry date ret=" << ret << dendl; + return ret; + } + + ldpp_dout(dpp, 5) << "Updated Restore expiry time to: " << expiration_date << " , cur_time: " << cur_time << ", restore_interval: " << cct->_conf->rgw_restore_debug_interval << dendl; + pobj->set_atomic(false); + + + return ret; +} + int Restore::restore_obj_from_cloud(rgw::sal::Bucket* pbucket, rgw::sal::Object* pobj, rgw::sal::PlacementTier* tier, diff --git a/src/rgw/rgw_restore.h b/src/rgw/rgw_restore.h index a5e72df58826..57409a128c22 100644 --- a/src/rgw/rgw_restore.h +++ b/src/rgw/rgw_restore.h @@ -133,6 +133,15 @@ public: optional_yield y, const rgw::sal::RGWRestoreStatus& restore_status); + /** Calculate expiration date based on expiry days */ + void get_expiration_date(const DoutPrefixProvider* dpp, + int expiry_days, ceph::real_time& exp_date); + + /** Update expiry date for temp restored copies */ + int update_cloud_restore_exp_date(rgw::sal::Bucket* pbucket, + rgw::sal::Object* pobj, std::optional days, + const DoutPrefixProvider* dpp, optional_yield y); + /** Given , restore the object from the cloud-tier. In case the * object cannot be restored immediately, save that restore state(/entry) * to be procesed later by RestoreWorker thread. */ -- 2.47.3