fix(ecstore): start with unresolved Kubernetes peers (#5460)

* fix(ecstore): start with unresolved Kubernetes peers

* fix(ecstore): infer Kubernetes endpoint identity safely

* fix(ecstore): fail closed on unsafe format migration

* fix(ecstore): reject poisoned format heal candidates

* fix(ecstore): reject unsafe legacy migration outliers

* fix(ecstore): resume interrupted format migrations

* fix(ecstore): preserve Kubernetes startup compatibility
This commit is contained in:
cxymds
2026-07-30 11:14:38 +08:00
committed by GitHub
parent 2e5cef513f
commit 67904a6c18
30 changed files with 3618 additions and 461 deletions
+22 -2
View File
@@ -92,7 +92,6 @@ fn resolve_domain(domain: &str) -> std::io::Result<HashSet<IpAddr>> {
(domain, 0)
.to_socket_addrs()
.map(|v| v.map(|v| v.ip()).collect::<HashSet<_>>())
.map_err(Error::other)
}
#[cfg(test)]
@@ -284,7 +283,7 @@ pub async fn get_host_ip(host: Host<&str>) -> std::io::Result<HashSet<IpAddr>> {
}
Err(err) => {
error!("Failed to resolve domain {domain} using system resolver, err: {err}");
Err(Error::other(err))
Err(err)
}
}
}
@@ -608,6 +607,27 @@ mod test {
assert!(get_host_ip(invalid_host).await.is_err());
}
#[tokio::test]
async fn test_get_host_ip_preserves_resolver_error_provenance() {
let _resolver_guard = set_mock_dns_resolver(|_| Err(IoError::from_raw_os_error(-3)));
let err = get_host_ip(Host::Domain("temporarily-unavailable.example"))
.await
.unwrap_err();
assert_eq!(err.raw_os_error(), Some(-3));
}
#[test]
fn test_resolve_domain_preserves_system_resolver_error_provenance() {
let _resolver_lock = DNS_RESOLVER_TEST_LOCK.lock().unwrap();
reset_dns_resolver_inner();
let err = resolve_domain("rustfs-resolver-provenance.invalid").unwrap_err();
assert_ne!(err.kind(), std::io::ErrorKind::Other, "system resolver error was wrapped: {err}");
}
#[test]
fn test_dns_cache_entry_expiry() {
let ips: HashSet<IpAddr> = [IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))].into_iter().collect();
+11 -2
View File
@@ -360,7 +360,7 @@ pub fn find_ellipses_patterns(arg: &str) -> Result<ArgPattern> {
Some(caps) => caps,
None => {
return Err(Error::other(format!(
"Invalid ellipsis format in ({arg}), Ellipsis range must be provided in format {{N...M}} where N and M are decimal or hexadecimal positive integers, M must be greater than N, with a maximum expanded range size of {MAX_ELLIPSES_RANGE_SIZE}"
"Invalid ellipsis format. Ellipsis range must be provided in format {{N...M}} where N and M are decimal or hexadecimal positive integers, M must be greater than N, with a maximum expanded range size of {MAX_ELLIPSES_RANGE_SIZE}"
)));
}
};
@@ -399,7 +399,7 @@ pub fn find_ellipses_patterns(arg: &str) -> Result<ArgPattern> {
|| p.suffix.contains(CLOSE_BRACES)
{
return Err(Error::other(format!(
"Invalid ellipsis format in ({arg}), Ellipsis range must be provided in format {{N...M}} where N and M are decimal or hexadecimal positive integers, M must be greater than N, with a maximum expanded range size of {MAX_ELLIPSES_RANGE_SIZE}"
"Invalid ellipsis format. Ellipsis range must be provided in format {{N...M}} where N and M are decimal or hexadecimal positive integers, M must be greater than N, with a maximum expanded range size of {MAX_ELLIPSES_RANGE_SIZE}"
)));
}
}
@@ -938,6 +938,15 @@ mod tests {
assert!(err.to_string().contains("decimal or hexadecimal"), "unexpected error message: {err}");
}
#[test]
fn test_find_ellipses_patterns_leftover_brace_error_does_not_echo_input() {
let err = find_ellipses_patterns("http://:brace-secret@server/{1...2}}").unwrap_err();
assert!(
!err.to_string().contains("brace-secret"),
"ellipsis error leaked endpoint credentials: {err}"
);
}
#[test]
fn test_parse_ellipses_range_rejects_oversized_ranges() {
let err = parse_ellipses_range("{0...10000}").unwrap_err();