]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
rgw/restore: Update expiry-date of restored copies
authorSoumya Koduri <skoduri@redhat.com>
Thu, 31 Jul 2025 19:19:44 +0000 (00:49 +0530)
committerSoumya Koduri <skoduri@redhat.com>
Fri, 15 Aug 2025 18:37:52 +0000 (00:07 +0530)
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 <skoduri@redhat.com>
doc/radosgw/cloud-restore.rst
src/rgw/driver/rados/rgw_rados.cc
src/rgw/driver/rados/rgw_sal_rados.cc
src/rgw/rgw_op.cc
src/rgw/rgw_restore.cc
src/rgw/rgw_restore.h

index 3afe0c50a4f0b3d6f651f2ae25b734b1feb0c80f..6280720a2d456e1725a453afaf8495f30c13ebed 100644 (file)
@@ -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 $
index cfd897aabc014f0e95ba183f52870c28c417adaa..02dfb31947c71aab5379f7233444530d297d9432 100644 (file)
@@ -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);
index 5c19e9223a716a99be7068fddcf8d7830d016bcd..fe8094316fba0de2ecb02fed8dcd17e847eb12d1 100644 (file)
@@ -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<rgw::sal::Object::ReadOp> 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<string, bufferlist> 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;
 }
index 52e1b8f2d1de1d590a0524e4199581b16221ebaf..421c266b6d1587f291aafe4d321a0e8056cb36b5 100644 (file)
@@ -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<int>(rgw::sal::RGWRestoreStatus::CloudRestored);
     } else { // first time restore or previous restore failed.
       // Restore the object.
index 5f4e707c4799d8aa22904c056ba0596678d9f6eb..e29637a72b9ec442eb2a8f1739cc49ff0eb698f2 100644 (file)
@@ -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<uint64_t> 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,
index a5e72df588263ad0010e4ff1b2461af6bb01be14..57409a128c2268dfa22fe573157edda79e715517 100644 (file)
@@ -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<uint64_t> days,
+                                            const DoutPrefixProvider* dpp, optional_yield y);
+
   /** Given <bucket, obj>, 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. */