fix(heal):heal failed replication via must_replicate instead of check replicate_delete (#2072)

This commit is contained in:
LeonWang0735
2026-03-05 15:47:36 +08:00
committed by GitHub
parent b73059dcf2
commit a0503168d4
3 changed files with 74 additions and 2 deletions

View File

@@ -862,7 +862,6 @@ fn should_force_path_style(target: &BucketTarget) -> bool {
// Explicit path-style or legacy boolean-like values.
"path" | "on" | "true" => true,
// `auto` and empty are defaulted to path-style for custom S3-compatible endpoints.
// RustFS/MinIO-style deployments typically do not configure virtual-hosted-style routing.
"auto" | "" => true,
// Unknown values: prefer compatibility with S3-compatible services.
_ => true,

View File

@@ -590,6 +590,10 @@ impl ReplicationResyncer {
}
}
fn heal_should_use_check_replicate_delete(oi: &ObjectInfo) -> bool {
oi.delete_marker || (!oi.replication_status.is_empty() && oi.replication_status != ReplicationStatusType::Failed)
}
pub async fn get_heal_replicate_object_info(oi: &ObjectInfo, rcfg: &ReplicationConfig) -> ReplicateObjectInfo {
let mut oi = oi.clone();
let mut user_defined = oi.user_defined.clone();
@@ -617,7 +621,7 @@ pub async fn get_heal_replicate_object_info(oi: &ObjectInfo, rcfg: &ReplicationC
}
}
let dsc = if oi.delete_marker || !oi.replication_status.is_empty() {
let dsc = if heal_should_use_check_replicate_delete(&oi) {
check_replicate_delete(
oi.bucket.as_str(),
&ObjectToDelete {
@@ -2965,6 +2969,7 @@ fn get_replication_action(oi1: &ObjectInfo, oi2: &HeadObjectOutput, op_type: Rep
#[cfg(test)]
mod tests {
use super::*;
use uuid::Uuid;
#[test]
fn test_part_range_spec_from_actual_size() {
@@ -2979,4 +2984,71 @@ mod tests {
assert!(part_range_spec_from_actual_size(0, 0).is_err());
assert!(part_range_spec_from_actual_size(0, -1).is_err());
}
#[test]
fn test_heal_should_use_check_replicate_delete_failed_non_delete_marker() {
let oi = ObjectInfo {
bucket: "b".to_string(),
name: "obj".to_string(),
delete_marker: false,
replication_status: ReplicationStatusType::Failed,
..Default::default()
};
assert!(
!heal_should_use_check_replicate_delete(&oi),
"Failed non-delete-marker object must use must_replicate path so it can be re-queued for heal"
);
}
#[test]
fn test_heal_should_use_check_replicate_delete_pending_uses_delete_path() {
let oi = ObjectInfo {
bucket: "b".to_string(),
name: "obj".to_string(),
delete_marker: false,
replication_status: ReplicationStatusType::Pending,
..Default::default()
};
assert!(
heal_should_use_check_replicate_delete(&oi),
"Pending (non-Failed) status with non-empty replication uses check_replicate_delete path"
);
}
#[test]
fn test_heal_should_use_check_replicate_delete_delete_marker() {
let oi = ObjectInfo {
bucket: "b".to_string(),
name: "obj".to_string(),
delete_marker: true,
replication_status: ReplicationStatusType::Failed,
..Default::default()
};
assert!(
heal_should_use_check_replicate_delete(&oi),
"Delete marker always uses check_replicate_delete path"
);
}
#[tokio::test]
async fn test_get_heal_replicate_object_info_failed_object_returns_heal_roi() {
let oi = ObjectInfo {
bucket: "test-bucket".to_string(),
name: "key".to_string(),
delete_marker: false,
replication_status: ReplicationStatusType::Failed,
version_id: Some(Uuid::nil()),
mod_time: Some(OffsetDateTime::now_utc()),
..Default::default()
};
let rcfg = ReplicationConfig::new(None, None);
let roi = get_heal_replicate_object_info(&oi, &rcfg).await;
assert_eq!(roi.replication_status, ReplicationStatusType::Failed);
assert_eq!(roi.op_type, ReplicationType::Heal);
assert!(
roi.dsc.replicate_any() || roi.dsc.targets_map.is_empty(),
"With no replication config, dsc may be empty; with config, replicate_any() would be true and queueing would occur"
);
}
}

View File

@@ -2667,6 +2667,7 @@ impl DefaultObjectUsecase {
}
if obj_info.replication_status == ReplicationStatusType::Replica
|| obj_info.replication_status == ReplicationStatusType::Pending
|| obj_info.version_purge_status == VersionPurgeStatusType::Pending
{
schedule_replication_delete(DeletedObjectReplicationInfo {