From c20b3c7f1992c9cf8c823d73883e78885568915e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Wed, 18 Mar 2026 12:54:41 +0800 Subject: [PATCH] fix(ecstore): handle EODM rules without due date (#2198) --- .../ecstore/src/bucket/lifecycle/lifecycle.rs | 63 +++++++++++++++---- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/crates/ecstore/src/bucket/lifecycle/lifecycle.rs b/crates/ecstore/src/bucket/lifecycle/lifecycle.rs index 8cd1d8c5e..5c5b26cc3 100644 --- a/crates/ecstore/src/bucket/lifecycle/lifecycle.rs +++ b/crates/ecstore/src/bucket/lifecycle/lifecycle.rs @@ -439,19 +439,20 @@ impl Lifecycle for BucketLifecycleConfiguration { if obj.is_latest && obj.expired_object_deletemarker() { if let Some(expiration) = rule.expiration.as_ref() { if expiration.expired_object_delete_marker.is_some_and(|v| v) { - if let Some(due) = expiration.next_due(obj) { - if now.unix_timestamp() >= due.unix_timestamp() { - events.push(Event { - action: IlmAction::DeleteVersionAction, - rule_id: rule.id.clone().unwrap_or_default(), - due: Some(due), - noncurrent_days: 0, - newer_noncurrent_versions: 0, - storage_class: "".into(), - }); - // Stop after scheduling an expired delete-marker event. - break; - } + // Preserve explicit date/days scheduling when configured. + // If only ExpiredObjectDeleteMarker=true is set, delete immediately. + let due = expiration.next_due(obj).unwrap_or(now); + if now.unix_timestamp() >= due.unix_timestamp() { + events.push(Event { + action: IlmAction::DeleteVersionAction, + rule_id: rule.id.clone().unwrap_or_default(), + due: Some(due), + noncurrent_days: 0, + newer_noncurrent_versions: 0, + storage_class: "".into(), + }); + // Stop after scheduling an expired delete-marker event. + break; } } } @@ -1231,6 +1232,42 @@ mod tests { assert_eq!(event.due, Some(expected_expiry_time(base_time, 1))); } + #[tokio::test] + async fn expired_object_delete_marker_without_date_or_days_deletes_immediately() { + let base_time = OffsetDateTime::from_unix_timestamp(1_000_000).unwrap(); + let lc = BucketLifecycleConfiguration { + rules: vec![LifecycleRule { + status: ExpirationStatus::from_static(ExpirationStatus::ENABLED), + expiration: Some(LifecycleExpiration { + expired_object_delete_marker: Some(true), + ..Default::default() + }), + abort_incomplete_multipart_upload: None, + filter: None, + id: Some("rule-expired-del-marker-immediate".to_string()), + noncurrent_version_expiration: None, + noncurrent_version_transitions: None, + prefix: None, + transitions: None, + }], + }; + + let opts = ObjectOpts { + name: "obj".to_string(), + mod_time: Some(base_time), + is_latest: true, + delete_marker: true, + num_versions: 1, + version_id: Some(Uuid::new_v4()), + ..Default::default() + }; + + let now = base_time + Duration::days(2); + let event = lc.eval_inner(&opts, now, 0).await; + assert_eq!(event.action, IlmAction::DeleteVersionAction); + assert_eq!(event.due, Some(now)); + } + #[tokio::test] async fn expired_object_delete_marker_date_based_not_yet_due() { // A date-based rule that has not yet reached its expiry date must not