mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 21:26:28 +00:00
fix(ilm): implement expire_restored delete semantics for restore expiry (#4950)
DeleteRestoredAction is supposed to demote a restored object back to its pure transitioned state: remove only the local restored copy, strip the x-amz-restore headers, and leave the version (and the remote tier data) untouched. expire_transitioned_object set opts.transition.expire_restored accordingly, but no delete path ever read the flag, so delete_object ran an ordinary delete: on unversioned buckets the whole object vanished and the free-version record scheduled remote tier cleanup (tier data loss); on versioned buckets the latest version got a spurious delete marker that replication propagated. Route expire_restored explicitly in SetDisks::delete_object before delete-marker resolution and replication dispatch: target the found version with FileInfo.expire_restored=true and return early. The FileMeta::delete_version layer already implements the semantics (strip restore headers, keep the version, hand back the local data dir); this wires it up. Also fix the action matching in expire_transitioned_object (extracted into transitioned_object_delete_opts): DeleteRestoredVersionAction previously fell through to the full transitioned-object delete, which removed the remote tier data of a noncurrent restored version. It now routes through the same restored-copy cleanup with the exact version id, matching MinIO's Action.DeleteVersioned()/DeleteRestored() dispatch. Re-enable test_restore_chain_local_read_expiry_keeps_remote_and_allows_ re_restore in the ILM Integration (serial) lane; add unit tests pinning the event->options routing and the filemeta expire_restored branch. Closes rustfs/backlog#1302
This commit is contained in:
@@ -1930,6 +1930,31 @@ impl crate::storage_api_contracts::object::ObjectOperations for SetDisks {
|
||||
check_object_lock_delete(bucket, object, &goi, &opts).await?;
|
||||
}
|
||||
|
||||
if opts.transition.expire_restored {
|
||||
// Restore-expiry (DeleteRestoredAction / DeleteRestoredVersionAction)
|
||||
// must only drop the local restored copy and strip the x-amz-restore
|
||||
// headers; the version itself stays transitioned (status=complete)
|
||||
// and keeps serving GETs from the tier. Route it before delete-marker
|
||||
// resolution and replication dispatch: a delete marker would hide the
|
||||
// version, a replicated delete would remove it on the target, and a
|
||||
// free-version record would schedule remote tier cleanup.
|
||||
if !version_found {
|
||||
return Err(gerr.unwrap_or_else(|| StorageError::ObjectNotFound(bucket.to_string(), object.to_string())));
|
||||
}
|
||||
let dfi = FileInfo {
|
||||
name: object.to_string(),
|
||||
version_id: goi.version_id,
|
||||
mod_time: Some(opts.mod_time.unwrap_or_else(OffsetDateTime::now_utc)),
|
||||
expire_restored: true,
|
||||
..Default::default()
|
||||
};
|
||||
self.delete_object_version(bucket, object, &dfi, false)
|
||||
.await
|
||||
.map_err(|e| to_object_err(e, vec![bucket, object]))?;
|
||||
self.invalidate_get_object_metadata_cache(bucket, object).await;
|
||||
return Ok(ObjectInfo::from_file_info(&dfi, bucket, object, opts.versioned || opts.version_suspended));
|
||||
}
|
||||
|
||||
let otd = ObjectToDelete {
|
||||
object_name: object.to_string(),
|
||||
version_id: opts
|
||||
|
||||
Reference in New Issue
Block a user