mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-28 16:07:05 +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:
@@ -112,6 +112,7 @@ const EVENT_LIFECYCLE_WORKER_STATE: &str = "lifecycle_worker_state";
|
||||
const EVENT_LIFECYCLE_TRANSITION_COMPENSATION: &str = "lifecycle_transition_compensation";
|
||||
const EVENT_LIFECYCLE_STALE_MULTIPART_CLEANUP: &str = "lifecycle_stale_multipart_cleanup";
|
||||
const EVENT_LIFECYCLE_SCAN_SKIPPED: &str = "lifecycle_scan_skipped";
|
||||
const EVENT_LIFECYCLE_EVALUATION_FAILED: &str = "lifecycle_evaluation_failed";
|
||||
const EVENT_LIFECYCLE_TIER_AUDIT: &str = "lifecycle_tier_audit";
|
||||
const EVENT_LIFECYCLE_TIER_OPERATION_FAILED: &str = "lifecycle_tier_operation_failed";
|
||||
const EVENT_LIFECYCLE_DELETE_FAILED: &str = "lifecycle_delete_failed";
|
||||
@@ -2647,12 +2648,25 @@ pub async fn enqueue_immediate_expiry(oi: &ObjectInfo, src: LcEventSrc) {
|
||||
let mut object_infos = Vec::new();
|
||||
|
||||
loop {
|
||||
let Ok(page) = api
|
||||
let page = match api
|
||||
.clone()
|
||||
.list_object_versions(&oi.bucket, &oi.name, marker.clone(), version_marker.clone(), None, 1000)
|
||||
.list_object_versions_for_lifecycle(&oi.bucket, &oi.name, marker.clone(), version_marker.clone(), None, 1000)
|
||||
.await
|
||||
else {
|
||||
return;
|
||||
{
|
||||
Ok(page) => page,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
event = EVENT_LIFECYCLE_EVALUATION_FAILED,
|
||||
component = LOG_COMPONENT_ECSTORE,
|
||||
subsystem = LOG_SUBSYSTEM_LIFECYCLE,
|
||||
bucket = %oi.bucket,
|
||||
object = %oi.name,
|
||||
error = %err,
|
||||
reason = "list_versions_failed",
|
||||
"Failed to load lifecycle version group"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
object_infos.extend(page.objects.into_iter().filter(|object| object.name == oi.name));
|
||||
@@ -2677,12 +2691,27 @@ pub async fn enqueue_immediate_expiry(oi: &ObjectInfo, src: LcEventSrc) {
|
||||
.iter()
|
||||
.map(lifecycle::object_opts_from_object_info)
|
||||
.collect::<Vec<ObjectOpts>>();
|
||||
let Ok(events) = Evaluator::new(Arc::new(lifecycle))
|
||||
let events = match Evaluator::new(Arc::new(lifecycle))
|
||||
.with_lock_retention(lock_config)
|
||||
.eval(&object_opts)
|
||||
.await
|
||||
else {
|
||||
return;
|
||||
{
|
||||
Ok(events) => events,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
event = EVENT_LIFECYCLE_EVALUATION_FAILED,
|
||||
component = LOG_COMPONENT_ECSTORE,
|
||||
subsystem = LOG_SUBSYSTEM_LIFECYCLE,
|
||||
bucket = %oi.bucket,
|
||||
object = %oi.name,
|
||||
expected_version_count = oi.num_versions,
|
||||
observed_version_count = object_infos.len(),
|
||||
error = %err,
|
||||
reason = "version_group_evaluation_failed",
|
||||
"Failed to evaluate lifecycle version group"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut to_delete_objs = Vec::new();
|
||||
@@ -3099,10 +3128,16 @@ async fn enqueue_expiry_for_existing_object_group(
|
||||
Ok(events) => events,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
event = EVENT_LIFECYCLE_EVALUATION_FAILED,
|
||||
component = LOG_COMPONENT_ECSTORE,
|
||||
subsystem = LOG_SUBSYSTEM_LIFECYCLE,
|
||||
bucket = context.bucket,
|
||||
object = %object_infos[0].name,
|
||||
expected_version_count = object_infos[0].num_versions,
|
||||
observed_version_count = object_infos.len(),
|
||||
error = %err,
|
||||
"failed to evaluate lifecycle events for existing object versions"
|
||||
reason = "version_group_evaluation_failed",
|
||||
"Failed to evaluate lifecycle version group"
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -3210,7 +3245,7 @@ pub async fn enqueue_expiry_for_existing_objects(api: Arc<ECStore>, bucket: &str
|
||||
loop {
|
||||
let page = api
|
||||
.clone()
|
||||
.list_object_versions(bucket, "", marker.clone(), version_marker.clone(), None, 1000)
|
||||
.list_object_versions_for_lifecycle(bucket, "", marker.clone(), version_marker.clone(), None, 1000)
|
||||
.await?;
|
||||
|
||||
for object in page.objects {
|
||||
@@ -3835,6 +3870,25 @@ pub async fn eval_action_from_lifecycle(
|
||||
}
|
||||
|
||||
if lifecycle_action_blocked_by_replication(event.action, oi) {
|
||||
let reason = if oi.version_purge_status.is_pending() {
|
||||
"version_purge_pending"
|
||||
} else if oi.replication_status == ReplicationStatusType::Failed {
|
||||
"replication_failed"
|
||||
} else {
|
||||
"replication_pending"
|
||||
};
|
||||
debug!(
|
||||
event = EVENT_LIFECYCLE_SCAN_SKIPPED,
|
||||
component = LOG_COMPONENT_ECSTORE,
|
||||
subsystem = LOG_SUBSYSTEM_LIFECYCLE,
|
||||
object = %oi.name,
|
||||
version_id = ?oi.version_id,
|
||||
action = ?event.action,
|
||||
replication_status = ?oi.replication_status,
|
||||
version_purge_status = ?oi.version_purge_status,
|
||||
reason,
|
||||
"Skipped lifecycle action because replication is not terminal"
|
||||
);
|
||||
return lifecycle::Event::default();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user