mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-19 19:16:17 +00:00
fix(admin): correct pool lifecycle status mapping (#3823)
This commit is contained in:
@@ -277,6 +277,9 @@ fn first_resumable_decommission_queue_indices(meta: &PoolMeta) -> Vec<usize> {
|
||||
let mut indices = Vec::new();
|
||||
for (idx, pool) in meta.pools.iter().enumerate() {
|
||||
if let Some(decommission) = &pool.decommission {
|
||||
if !decommission.has_decommission_state() {
|
||||
continue;
|
||||
}
|
||||
if decommission.complete {
|
||||
continue;
|
||||
}
|
||||
@@ -398,14 +401,14 @@ fn is_decommission_active(complete: bool, failed: bool, canceled: bool) -> bool
|
||||
|
||||
pub(crate) fn pool_meta_has_active_decommission(meta: &PoolMeta) -> bool {
|
||||
meta.pools.iter().any(|pool| {
|
||||
pool.decommission
|
||||
.as_ref()
|
||||
.is_some_and(|info| is_decommission_active(info.complete, info.failed, info.canceled))
|
||||
pool.decommission.as_ref().is_some_and(|info| {
|
||||
info.has_decommission_state() && is_decommission_active(info.complete, info.failed, info.canceled)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn is_decommission_suspended(info: &PoolDecommissionInfo) -> bool {
|
||||
!info.queued
|
||||
info.has_decommission_state() && !info.queued
|
||||
}
|
||||
|
||||
fn validate_decommission_terminal_state(complete: bool, failed: bool, canceled: bool) -> Result<()> {
|
||||
@@ -438,6 +441,9 @@ fn decommission_start_pool_state(pool: Option<&PoolStatus>) -> DecommissionStart
|
||||
let Some(info) = pool.decommission.as_ref() else {
|
||||
return DecommissionStartPoolState::Active;
|
||||
};
|
||||
if !info.has_decommission_state() {
|
||||
return DecommissionStartPoolState::Active;
|
||||
}
|
||||
|
||||
if info.complete {
|
||||
DecommissionStartPoolState::Decommissioned
|
||||
@@ -1540,7 +1546,7 @@ impl PoolMeta {
|
||||
let (decommission_present, complete, failed, canceled) = pool
|
||||
.decommission
|
||||
.as_ref()
|
||||
.map(|info| (true, info.complete, info.failed, info.canceled))
|
||||
.map(|info| (info.has_decommission_state(), info.complete, info.failed, info.canceled))
|
||||
.unwrap_or((false, false, false, false));
|
||||
|
||||
ensure_decommission_clear_allowed(true, decommission_present, complete, failed, canceled)?;
|
||||
@@ -1806,6 +1812,9 @@ impl PoolMeta {
|
||||
let mut new_pools = Vec::new();
|
||||
for pool in &self.pools {
|
||||
if let Some(decommission) = &pool.decommission {
|
||||
if !decommission.has_decommission_state() {
|
||||
continue;
|
||||
}
|
||||
if decommission.complete || decommission.failed || decommission.canceled {
|
||||
// Recovery is not required when:
|
||||
// - Decommissioning completed
|
||||
@@ -1878,6 +1887,27 @@ pub struct PoolDecommissionInfo {
|
||||
}
|
||||
|
||||
impl PoolDecommissionInfo {
|
||||
pub fn has_decommission_state(&self) -> bool {
|
||||
self.complete
|
||||
|| self.failed
|
||||
|| self.canceled
|
||||
|| self.queued
|
||||
|| self.start_time.is_some()
|
||||
|| self.start_size > 0
|
||||
|| !self.queued_buckets.is_empty()
|
||||
|| !self.decommissioned_buckets.is_empty()
|
||||
|| !self.bucket.is_empty()
|
||||
|| !self.prefix.is_empty()
|
||||
|| !self.object.is_empty()
|
||||
|| !self.stage.is_empty()
|
||||
|| self.items_decommissioned > 0
|
||||
|| self.items_decommission_failed > 0
|
||||
|| self.bytes_done > 0
|
||||
|| self.bytes_failed > 0
|
||||
|| self.terminal_reload_attempt_at.is_some()
|
||||
|| !self.terminal_reload_failures.is_empty()
|
||||
}
|
||||
|
||||
fn counted_items(&self) -> usize {
|
||||
self.items_decommissioned.saturating_add(self.items_decommission_failed)
|
||||
}
|
||||
@@ -2215,7 +2245,11 @@ impl ECStore {
|
||||
let (pool_present, decommission_present, terminal) = if let Some(pool) = lock.pools.get(idx) {
|
||||
if let Some(info) = pool.decommission.as_ref() {
|
||||
already_canceled = info.canceled;
|
||||
(true, true, should_reject_decommission_cancel_as_terminal(info.complete, info.failed))
|
||||
(
|
||||
true,
|
||||
info.has_decommission_state(),
|
||||
should_reject_decommission_cancel_as_terminal(info.complete, info.failed),
|
||||
)
|
||||
} else {
|
||||
(true, false, false)
|
||||
}
|
||||
@@ -2339,6 +2373,7 @@ impl ECStore {
|
||||
let pool_meta = self.pool_meta.read().await;
|
||||
for pool in pool_meta.pools.iter() {
|
||||
if let Some(ref info) = pool.decommission
|
||||
&& info.has_decommission_state()
|
||||
&& !info.complete
|
||||
&& !info.failed
|
||||
&& !info.canceled
|
||||
@@ -5101,7 +5136,10 @@ mod pools_tests {
|
||||
..Default::default()
|
||||
};
|
||||
let mut active = previous.clone();
|
||||
active.pools[0].decommission = Some(PoolDecommissionInfo::default());
|
||||
active.pools[0].decommission = Some(PoolDecommissionInfo {
|
||||
start_time: Some(OffsetDateTime::UNIX_EPOCH),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
assert!(active.is_suspended(0));
|
||||
assert_eq!(
|
||||
@@ -6033,13 +6071,16 @@ mod pools_tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_meta_has_active_decommission_counts_active_and_queued_states() {
|
||||
fn test_pool_meta_has_active_decommission_counts_running_and_queued_states() {
|
||||
let active_meta = PoolMeta {
|
||||
pools: vec![PoolStatus {
|
||||
id: 0,
|
||||
cmd_line: "pool-0".to_string(),
|
||||
last_update: OffsetDateTime::UNIX_EPOCH,
|
||||
decommission: Some(PoolDecommissionInfo::default()),
|
||||
decommission: Some(PoolDecommissionInfo {
|
||||
start_time: Some(OffsetDateTime::UNIX_EPOCH),
|
||||
..Default::default()
|
||||
}),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
@@ -6060,6 +6101,27 @@ mod pools_tests {
|
||||
assert!(pool_meta_has_active_decommission(&queued_meta));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_meta_has_active_decommission_ignores_capacity_placeholder() {
|
||||
let meta = PoolMeta {
|
||||
pools: vec![PoolStatus {
|
||||
id: 0,
|
||||
cmd_line: "pool-0".to_string(),
|
||||
last_update: OffsetDateTime::UNIX_EPOCH,
|
||||
decommission: Some(PoolDecommissionInfo {
|
||||
total_size: 100,
|
||||
current_size: 75,
|
||||
..Default::default()
|
||||
}),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert!(!pool_meta_has_active_decommission(&meta));
|
||||
assert!(!meta.is_suspended(0));
|
||||
assert_eq!(decommission_start_pool_state(meta.pools.first()), DecommissionStartPoolState::Active);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pool_meta_has_active_decommission_ignores_terminal_states() {
|
||||
let terminal_meta = PoolMeta {
|
||||
@@ -6158,6 +6220,7 @@ mod pools_tests {
|
||||
cmd_line: "pool-0".to_string(),
|
||||
last_update: OffsetDateTime::UNIX_EPOCH,
|
||||
decommission: Some(PoolDecommissionInfo {
|
||||
start_time: Some(OffsetDateTime::UNIX_EPOCH),
|
||||
complete: false,
|
||||
failed: false,
|
||||
canceled: false,
|
||||
|
||||
@@ -39,6 +39,13 @@ pub(super) fn validate_rebalance_disk_stats_coverage(disk_stats: &[DiskStat]) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pool_rebalance_status_from_meta(meta: Option<&RebalanceMeta>, pool_index: usize) -> (RebalStatus, bool) {
|
||||
meta.and_then(|meta| meta.pool_stats.get(pool_index))
|
||||
.filter(|pool_stat| pool_stat.participating)
|
||||
.map(|pool_stat| (pool_stat.info.status, pool_stat.info.stopping))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
impl ECStore {
|
||||
pub(super) async fn save_rebalance_meta_with_merge<S>(
|
||||
&self,
|
||||
@@ -442,11 +449,7 @@ impl ECStore {
|
||||
|
||||
pub async fn pool_rebalance_status(&self, pool_index: usize) -> (RebalStatus, bool) {
|
||||
let rebalance_meta = self.rebalance_meta.read().await;
|
||||
rebalance_meta
|
||||
.as_ref()
|
||||
.and_then(|meta| meta.pool_stats.get(pool_index))
|
||||
.map(|pool_stat| (pool_stat.info.status, pool_stat.info.stopping))
|
||||
.unwrap_or_default()
|
||||
pool_rebalance_status_from_meta(rebalance_meta.as_ref(), pool_index)
|
||||
}
|
||||
|
||||
pub async fn current_rebalance_id(&self) -> Option<String> {
|
||||
@@ -530,3 +533,38 @@ impl ECStore {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn pool_rebalance_status_ignores_non_participating_pool_state() {
|
||||
let meta = RebalanceMeta {
|
||||
pool_stats: vec![
|
||||
RebalanceStats {
|
||||
participating: false,
|
||||
info: RebalanceInfo {
|
||||
status: RebalStatus::Started,
|
||||
stopping: true,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
RebalanceStats {
|
||||
participating: true,
|
||||
info: RebalanceInfo {
|
||||
status: RebalStatus::Started,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(pool_rebalance_status_from_meta(Some(&meta), 0), (RebalStatus::None, false));
|
||||
assert_eq!(pool_rebalance_status_from_meta(Some(&meta), 1), (RebalStatus::Started, false));
|
||||
assert_eq!(pool_rebalance_status_from_meta(Some(&meta), 2), (RebalStatus::None, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ fn pool_meta_has_active_decommission(meta: &PoolMeta) -> bool {
|
||||
meta.pools.iter().any(|pool| {
|
||||
pool.decommission
|
||||
.as_ref()
|
||||
.is_some_and(|info| !info.complete && !info.failed && !info.canceled)
|
||||
.is_some_and(|info| info.has_decommission_state() && !info.complete && !info.failed && !info.canceled)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user