diff --git a/rustfs/src/admin/handlers/site_replication.rs b/rustfs/src/admin/handlers/site_replication.rs index ac796cee3..e68016167 100644 --- a/rustfs/src/admin/handlers/site_replication.rs +++ b/rustfs/src/admin/handlers/site_replication.rs @@ -1594,8 +1594,27 @@ fn remove_sites(mut state: SiteReplicationState, req: SRRemoveReq) -> SiteReplic return state; } - let names: Vec = req.site_names.into_iter().collect(); - state.peers.retain(|_, peer| !names.iter().any(|name| name == &peer.name)); + let names: HashSet = req.site_names.into_iter().collect(); + if names.contains(&state.name) { + state.peers.clear(); + state.resync_status.clear(); + state.updated_at = Some(OffsetDateTime::now_utc()); + return state; + } + + let removed_deployment_ids: Vec = state + .peers + .iter() + .filter(|(_, peer)| names.contains(&peer.name)) + .map(|(deployment_id, _)| deployment_id.clone()) + .collect(); + for deployment_id in removed_deployment_ids { + state.peers.remove(&deployment_id); + state.resync_status.remove(&deployment_id); + } + state + .resync_status + .retain(|deployment_id, _| state.peers.contains_key(deployment_id)); state.updated_at = Some(OffsetDateTime::now_utc()); state } @@ -3295,6 +3314,122 @@ mod tests { assert!(status.err_detail.contains("403 Forbidden")); } + #[test] + fn test_remove_sites_drops_resync_status_for_removed_peer() { + let mut state = SiteReplicationState { + name: "local".to_string(), + ..Default::default() + }; + state.peers.insert( + "local-deployment".to_string(), + PeerInfo { + deployment_id: "local-deployment".to_string(), + ..peer("local", "https://local.example.com") + }, + ); + state.peers.insert( + "remote-a-deployment".to_string(), + PeerInfo { + deployment_id: "remote-a-deployment".to_string(), + ..peer("remote-a", "https://remote-a.example.com") + }, + ); + state.peers.insert( + "remote-b-deployment".to_string(), + PeerInfo { + deployment_id: "remote-b-deployment".to_string(), + ..peer("remote-b", "https://remote-b.example.com") + }, + ); + state.resync_status.insert( + "remote-a-deployment".to_string(), + SRResyncOpStatus { + resync_id: "stale-a".to_string(), + status: "success".to_string(), + ..Default::default() + }, + ); + state.resync_status.insert( + "remote-a-legacy-key".to_string(), + SRResyncOpStatus { + resync_id: "stale-a-legacy".to_string(), + status: "success".to_string(), + ..Default::default() + }, + ); + state.resync_status.insert( + "remote-b-deployment".to_string(), + SRResyncOpStatus { + resync_id: "active-b".to_string(), + status: "success".to_string(), + ..Default::default() + }, + ); + + let state = remove_sites( + state, + SRRemoveReq { + site_names: vec!["remote-a".to_string()], + ..Default::default() + }, + ); + + assert!(state.peers.contains_key("local-deployment")); + assert!(!state.peers.contains_key("remote-a-deployment")); + assert!(state.peers.contains_key("remote-b-deployment")); + assert!(!state.resync_status.contains_key("remote-a-deployment")); + assert!(!state.resync_status.contains_key("remote-a-legacy-key")); + assert!(state.resync_status.contains_key("remote-b-deployment")); + } + + #[test] + fn test_remove_sites_clears_state_when_local_site_is_removed() { + let mut state = SiteReplicationState { + name: "local".to_string(), + ..Default::default() + }; + state.peers.insert( + "local-deployment".to_string(), + PeerInfo { + deployment_id: "local-deployment".to_string(), + ..peer("local", "https://local.example.com") + }, + ); + state.peers.insert( + "remote-a-deployment".to_string(), + PeerInfo { + deployment_id: "remote-a-deployment".to_string(), + ..peer("remote-a", "https://remote-a.example.com") + }, + ); + state.peers.insert( + "remote-b-deployment".to_string(), + PeerInfo { + deployment_id: "remote-b-deployment".to_string(), + ..peer("remote-b", "https://remote-b.example.com") + }, + ); + state.resync_status.insert( + "remote-a-deployment".to_string(), + SRResyncOpStatus { + resync_id: "active-a".to_string(), + status: "success".to_string(), + ..Default::default() + }, + ); + + let state = remove_sites( + state, + SRRemoveReq { + site_names: vec!["local".to_string()], + ..Default::default() + }, + ); + + assert!(state.peers.is_empty()); + assert!(state.resync_status.is_empty()); + } + #[test] fn test_site_replication_remove_status_truncates_peer_error_detail() { let long_peer_body = "peer response body ".repeat(40);