diff --git a/crates/cli/src/commands/admin/info.rs b/crates/cli/src/commands/admin/info.rs index 0884302..bd621e1 100644 --- a/crates/cli/src/commands/admin/info.rs +++ b/crates/cli/src/commands/admin/info.rs @@ -542,10 +542,16 @@ fn print_cluster_info(info: &ClusterInfo, formatter: &Formatter) { // Object info if let Some(ref buckets) = info.buckets { - formatter.println(&format!(" Buckets: {}", buckets.count)); + formatter.println(&format!( + " Buckets: {}", + count_or_unavailable(buckets.count, buckets.error.as_deref()) + )); } if let Some(ref objects) = info.objects { - formatter.println(&format!(" Objects: {}", objects.count)); + formatter.println(&format!( + " Objects: {}", + count_or_unavailable(objects.count, objects.error.as_deref()) + )); } // Backend info @@ -802,6 +808,14 @@ fn value_or_unknown(value: &str) -> &str { if value.is_empty() { "unknown" } else { value } } +fn count_or_unavailable(count: u64, error: Option<&str>) -> String { + if error.is_some_and(|error| !error.trim().is_empty()) { + "unavailable".to_string() + } else { + count.to_string() + } +} + fn cluster_rustfs_version(info: &ClusterInfo) -> String { let versions = info .servers @@ -908,6 +922,16 @@ mod tests { assert!(value.get("usedCapacity").is_some()); } + #[test] + fn test_count_or_unavailable_distinguishes_unknown_from_zero() { + assert_eq!(count_or_unavailable(0, None), "0"); + assert_eq!(count_or_unavailable(42, Some("")), "42"); + assert_eq!( + count_or_unavailable(0, Some("data usage snapshot unavailable")), + "unavailable" + ); + } + #[test] fn test_format_bytes() { assert_eq!(format_bytes(0), "0 B"); diff --git a/crates/cli/tests/admin_info.rs b/crates/cli/tests/admin_info.rs index 1db6ba4..6202340 100644 --- a/crates/cli/tests/admin_info.rs +++ b/crates/cli/tests/admin_info.rs @@ -8,6 +8,7 @@ use std::time::Duration; use admin_support::{rc_binary, rc_host_alias, start_admin_test_server}; const BETA9_INFO_RESPONSE: &str = r#"{"info":{"mode":"distributed","deploymentID":"deployment-123","servers":[{"endpoint":"http://node1:9000","state":"online","version":"1.0.0-beta.9","drives":[{"endpoint":"http://node1:9000/data1","path":"/data1","state":"ok","totalspace":100,"usedspace":40,"availspace":60,"pool_index":1,"set_index":2,"disk_index":3}]}]},"admin_discovery":{"runtimeCapabilities":"/rustfs/admin/v4/runtime/capabilities","clusterSnapshot":"/rustfs/admin/v4/cluster/snapshot","extensionsCatalog":"/rustfs/admin/v4/extensions/catalog"}}"#; +const UNAVAILABLE_STATS_INFO_RESPONSE: &str = r#"{"info":{"mode":"distributed","deploymentID":"deployment-123","buckets":{"count":0,"error":"data usage snapshot unavailable"},"objects":{"count":0,"error":"data usage snapshot unavailable"},"servers":[]}}"#; #[test] fn cluster_info_displays_disk_location_indexes_from_snake_case_fields() { @@ -51,6 +52,45 @@ fn cluster_info_displays_disk_location_indexes_from_snake_case_fields() { handle.join().expect("admin test server finished"); } +#[test] +fn cluster_info_marks_unavailable_counts_instead_of_reporting_zero() { + let config_dir = tempfile::tempdir().expect("create config dir"); + let (endpoint, receiver, handle) = start_admin_test_server(UNAVAILABLE_STATS_INFO_RESPONSE); + + let output = Command::new(rc_binary()) + .args(["admin", "info", "cluster", "myalias"]) + .env("RC_CONFIG_DIR", config_dir.path()) + .env("RC_HOST_myalias", rc_host_alias(&endpoint)) + .output() + .expect("run rc command"); + + assert!( + output.status.success(), + "stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let stdout = String::from_utf8(output.stdout).expect("stdout should be UTF-8"); + assert!( + stdout.contains("Buckets: unavailable"), + "stdout: {stdout}" + ); + assert!( + stdout.contains("Objects: unavailable"), + "stdout: {stdout}" + ); + assert!(!stdout.contains("Buckets: 0"), "stdout: {stdout}"); + assert!(!stdout.contains("Objects: 0"), "stdout: {stdout}"); + + let request = receiver + .recv_timeout(Duration::from_secs(5)) + .expect("captured admin request"); + assert_eq!(request.method, "GET"); + assert_eq!(request.target, "/rustfs/admin/v3/info"); + + handle.join().expect("admin test server finished"); +} + #[test] fn server_info_reads_servers_from_beta9_info_response() { let config_dir = tempfile::tempdir().expect("create config dir");