mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-11 15:46:53 +00:00
fix(ilm): preserve lifecycle version groups (#5239)
* fix(ilm): evaluate complete version groups Co-Authored-By: heihutu <heihutu@gmail.com> * feat(ilm): log replication expiry blocks Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ilm): diagnose incomplete noncurrent chains Co-Authored-By: heihutu <heihutu@gmail.com> * test(ilm): cover purge-pending version groups Add regression coverage for lifecycle-only version listing so purge-pending versions stay present for ILM evaluation while the public ListObjectVersions projection remains filtered. Refs rustfs/backlog#1500 Co-Authored-By: heihutu <heihutu@gmail.com> * feat(ilm): log lifecycle evaluation failures Emit structured lifecycle_evaluation_failed events when version group loading or evaluation fails during immediate and existing-object expiry scans. Refs rustfs/backlog#1503 Co-Authored-By: heihutu <heihutu@gmail.com> * test(ilm): cover incomplete noncurrent chains Pin the fail-closed behavior for noncurrent expiration when successor_mod_time is missing and reuse a stable structured event name for that skip path. Refs rustfs/backlog#1502 Co-Authored-By: heihutu <heihutu@gmail.com> * test(ilm): cover one-day noncurrent expiry boundary Add a runtime regression test proving NoncurrentVersionExpiration Days=1 stays inactive before expected_expiry_time and deletes exactly at the computed due boundary. Refs rustfs/backlog#1504 Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ilm): keep transition checks after incomplete expiry Co-Authored-By: heihutu <heihutu@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -556,6 +556,29 @@ impl ObjectInfo {
|
||||
prefix: &str,
|
||||
delimiter: Option<String>,
|
||||
after_version_marker: Option<VersionMarker>,
|
||||
) -> Vec<ObjectInfo> {
|
||||
Self::from_meta_cache_entries_sorted_versions_with_purge(entries, bucket, prefix, delimiter, after_version_marker, false)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn from_meta_cache_entries_sorted_versions_for_lifecycle(
|
||||
entries: &MetaCacheEntriesSorted,
|
||||
bucket: &str,
|
||||
prefix: &str,
|
||||
delimiter: Option<String>,
|
||||
after_version_marker: Option<VersionMarker>,
|
||||
) -> Vec<ObjectInfo> {
|
||||
Self::from_meta_cache_entries_sorted_versions_with_purge(entries, bucket, prefix, delimiter, after_version_marker, true)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn from_meta_cache_entries_sorted_versions_with_purge(
|
||||
entries: &MetaCacheEntriesSorted,
|
||||
bucket: &str,
|
||||
prefix: &str,
|
||||
delimiter: Option<String>,
|
||||
after_version_marker: Option<VersionMarker>,
|
||||
include_version_purge: bool,
|
||||
) -> Vec<ObjectInfo> {
|
||||
let vcfg = get_versioning_config(bucket).await.ok();
|
||||
let mut objects = Vec::with_capacity(entries.entries().len());
|
||||
@@ -604,7 +627,7 @@ impl ObjectInfo {
|
||||
};
|
||||
|
||||
for fi in versions.iter() {
|
||||
if !fi.version_purge_status().is_empty() {
|
||||
if !include_version_purge && !fi.version_purge_status().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1064,6 +1087,67 @@ mod tests {
|
||||
assert_eq!(objects[0].num_versions, 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn lifecycle_versions_listing_preserves_purge_pending_versions() {
|
||||
let visible_version_id = Uuid::new_v4();
|
||||
let purge_version_id = Uuid::new_v4();
|
||||
let base_time = OffsetDateTime::now_utc();
|
||||
let mut fm = FileMeta::new();
|
||||
|
||||
fm.add_version(FileInfo {
|
||||
volume: "bucket".to_string(),
|
||||
name: "object".to_string(),
|
||||
version_id: Some(purge_version_id),
|
||||
mod_time: Some(base_time),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("version pending purge should be added");
|
||||
fm.add_version(FileInfo {
|
||||
volume: "bucket".to_string(),
|
||||
name: "object".to_string(),
|
||||
version_id: Some(visible_version_id),
|
||||
mod_time: Some(base_time + time::Duration::seconds(1)),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("visible version should be added");
|
||||
fm.delete_version(&FileInfo {
|
||||
volume: "bucket".to_string(),
|
||||
name: "object".to_string(),
|
||||
version_id: Some(purge_version_id),
|
||||
replication_state_internal: Some(crate::bucket::replication::replication_state_to_filemeta(&ReplicationState {
|
||||
version_purge_status_internal: Some("arn:target-a=PENDING;".to_string()),
|
||||
purge_targets: version_purge_statuses_map("arn:target-a=PENDING;"),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("version purge status should be persisted");
|
||||
|
||||
let entries = MetaCacheEntriesSorted {
|
||||
o: rustfs_filemeta::MetaCacheEntries(vec![Some(MetaCacheEntry {
|
||||
name: "object".to_string(),
|
||||
metadata: fm.marshal_msg().expect("metadata should marshal"),
|
||||
..Default::default()
|
||||
})]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let public_objects = ObjectInfo::from_meta_cache_entries_sorted_versions(&entries, "bucket", "", None, None).await;
|
||||
let lifecycle_objects =
|
||||
ObjectInfo::from_meta_cache_entries_sorted_versions_for_lifecycle(&entries, "bucket", "", None, None).await;
|
||||
|
||||
assert_eq!(public_objects.len(), 1);
|
||||
assert_eq!(public_objects[0].version_id, Some(visible_version_id));
|
||||
assert_eq!(public_objects[0].num_versions, 2);
|
||||
assert_eq!(lifecycle_objects.len(), 2);
|
||||
assert!(
|
||||
lifecycle_objects
|
||||
.iter()
|
||||
.any(|object| object.version_purge_status == VersionPurgeStatusType::Pending)
|
||||
);
|
||||
assert!(lifecycle_objects.iter().all(|object| object.num_versions == 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_actual_size_prefers_actual_size_field() {
|
||||
let info = ObjectInfo {
|
||||
|
||||
Reference in New Issue
Block a user