diff --git a/crates/ecstore/src/pools.rs b/crates/ecstore/src/pools.rs index 3e7211895..e81cba0a7 100644 --- a/crates/ecstore/src/pools.rs +++ b/crates/ecstore/src/pools.rs @@ -1384,28 +1384,109 @@ impl SetDisks { } } +fn is_disk_online_state(state: &str) -> bool { + // The disk state strings are produced from rustfs_utils::os::get_drive_stats or DiskError::to_string(). + // Conventionally, online is "ok"/"online" (may evolve). Be conservative: + // - Treat empty as unknown -> include it (to avoid dropping capacity). + // - Exclude explicit offline-ish states. + let s = state.trim().to_lowercase(); + if s.is_empty() { + return true; + } + if s.contains("offline") { + return false; + } + if s.contains("not found") || s.contains("disk not found") { + return false; + } + true +} + +fn fallback_total_capacity(disks: &[rustfs_madmin::Disk]) -> usize { + disks + .iter() + .filter(|d| is_disk_online_state(&d.state)) + .map(|d| d.total_space as usize) + .sum() +} + +fn fallback_free_capacity(disks: &[rustfs_madmin::Disk]) -> usize { + disks + .iter() + .filter(|d| is_disk_online_state(&d.state)) + .map(|d| d.available_space as usize) + .sum() +} + pub fn get_total_usable_capacity(disks: &[rustfs_madmin::Disk], info: &rustfs_madmin::StorageInfo) -> usize { - let mut capacity = 0; + // If backend info is missing or inconsistent, do a safe fallback to avoid reporting nonsense. + if info.backend.standard_sc_data.is_empty() { + return fallback_total_capacity(disks); + } + + let mut capacity = 0usize; + let mut matched_any = false; + for disk in disks.iter() { - if disk.pool_index < 0 || info.backend.standard_sc_data.len() <= disk.pool_index as usize { + if disk.pool_index < 0 { continue; } - if (disk.disk_index as usize) < info.backend.standard_sc_data[disk.pool_index as usize] { + let pool_idx = disk.pool_index as usize; + if info.backend.standard_sc_data.len() <= pool_idx { + continue; + } + + let usable_disks_per_set = info.backend.standard_sc_data[pool_idx]; + if usable_disks_per_set == 0 { + continue; + } + + if (disk.disk_index as usize) < usable_disks_per_set { + matched_any = true; capacity += disk.total_space as usize; } } - capacity + + if matched_any { + capacity + } else { + // Even if standard_sc_data exists, it might not match disk indexes due to upstream bugs. + // Fallback to summing all online disks to prevent under-reporting. + fallback_total_capacity(disks) + } } pub fn get_total_usable_capacity_free(disks: &[rustfs_madmin::Disk], info: &rustfs_madmin::StorageInfo) -> usize { - let mut capacity = 0; + if info.backend.standard_sc_data.is_empty() { + return fallback_free_capacity(disks); + } + + let mut capacity = 0usize; + let mut matched_any = false; + for disk in disks.iter() { - if disk.pool_index < 0 || info.backend.standard_sc_data.len() <= disk.pool_index as usize { + if disk.pool_index < 0 { continue; } - if (disk.disk_index as usize) < info.backend.standard_sc_data[disk.pool_index as usize] { + let pool_idx = disk.pool_index as usize; + if info.backend.standard_sc_data.len() <= pool_idx { + continue; + } + + let usable_disks_per_set = info.backend.standard_sc_data[pool_idx]; + if usable_disks_per_set == 0 { + continue; + } + + if (disk.disk_index as usize) < usable_disks_per_set { + matched_any = true; capacity += disk.available_space as usize; } } - capacity + + if matched_any { + capacity + } else { + fallback_free_capacity(disks) + } } diff --git a/crates/ecstore/src/set_disk.rs b/crates/ecstore/src/set_disk.rs index 52d33c3fe..389e3f16d 100644 --- a/crates/ecstore/src/set_disk.rs +++ b/crates/ecstore/src/set_disk.rs @@ -6817,10 +6817,17 @@ async fn get_storage_info(disks: &[Option], eps: &[Endpoint]) -> rust let mut disks = get_disks_info(disks, eps).await; disks.sort_by(|a, b| a.total_space.cmp(&b.total_space)); + // Provide minimal backend shape for callers. Do NOT guess parity here since it belongs to higher-level config. + // Missing/empty standard_sc_data will be handled by capacity fallback logic. + let drives_per_set = vec![eps.len()]; + let total_sets = vec![1]; + rustfs_madmin::StorageInfo { disks, backend: rustfs_madmin::BackendInfo { backend_type: rustfs_madmin::BackendByte::Erasure, + drives_per_set, + total_sets, ..Default::default() }, }