mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-27 16:48:58 +00:00
feat: enrich admin pools list response (#2853)
Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
@@ -172,10 +172,10 @@ impl Operation for ListPools {
|
||||
.await?;
|
||||
|
||||
let usecase = DefaultAdminUsecase::from_global();
|
||||
let pools_status = usecase.execute_list_pool_statuses().await.map_err(S3Error::from)?;
|
||||
let pool_items = usecase.execute_list_pools().await.map_err(S3Error::from)?;
|
||||
|
||||
let data = serde_json::to_vec(&pools_status)
|
||||
.map_err(|_e| S3Error::with_message(S3ErrorCode::InternalError, "parse accountInfo failed"))?;
|
||||
let data = serde_json::to_vec(&pool_items)
|
||||
.map_err(|_e| S3Error::with_message(S3ErrorCode::InternalError, "serialize pools list failed"))?;
|
||||
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
@@ -22,7 +22,7 @@ use rustfs_ecstore::admin_server_info::get_server_info;
|
||||
use rustfs_ecstore::data_usage::load_data_usage_from_backend;
|
||||
use rustfs_ecstore::endpoints::EndpointServerPools;
|
||||
use rustfs_ecstore::new_object_layer_fn;
|
||||
use rustfs_ecstore::pools::{PoolStatus, get_total_usable_capacity, get_total_usable_capacity_free};
|
||||
use rustfs_ecstore::pools::{PoolDecommissionInfo, PoolStatus, get_total_usable_capacity, get_total_usable_capacity_free};
|
||||
use rustfs_ecstore::store_api::StorageAPI;
|
||||
use rustfs_madmin::{Disk, InfoMessage, StorageInfo};
|
||||
use s3s::S3ErrorCode;
|
||||
@@ -61,6 +61,28 @@ pub struct QueryPoolStatusRequest {
|
||||
pub by_id: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct AdminPoolListItem {
|
||||
#[serde(rename = "id")]
|
||||
pub id: usize,
|
||||
#[serde(rename = "cmdline")]
|
||||
pub cmd_line: String,
|
||||
#[serde(rename = "lastUpdate", with = "time::serde::rfc3339")]
|
||||
pub last_update: time::OffsetDateTime,
|
||||
#[serde(rename = "totalSize")]
|
||||
pub total_size: usize,
|
||||
#[serde(rename = "currentSize")]
|
||||
pub current_size: usize,
|
||||
#[serde(rename = "usedSize")]
|
||||
pub used_size: usize,
|
||||
#[serde(rename = "used")]
|
||||
pub used: f64,
|
||||
#[serde(rename = "status")]
|
||||
pub status: String,
|
||||
#[serde(rename = "decommissionInfo")]
|
||||
pub decommission: Option<PoolDecommissionInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct DefaultAdminUsecase {
|
||||
context: Option<Arc<AppContext>>,
|
||||
@@ -76,6 +98,11 @@ impl DefaultAdminUsecase {
|
||||
const DISK_STATE_OK: &'static str = "ok";
|
||||
const DISK_STATE_UNFORMATTED: &'static str = "unformatted";
|
||||
const RUNTIME_STATE_RETURNING: &'static str = "returning";
|
||||
const POOL_STATUS_ACTIVE: &'static str = "active";
|
||||
const POOL_STATUS_CANCELED: &'static str = "canceled";
|
||||
const POOL_STATUS_COMPLETE: &'static str = "complete";
|
||||
const POOL_STATUS_FAILED: &'static str = "failed";
|
||||
const POOL_STATUS_RUNNING: &'static str = "running";
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn without_context() -> Self {
|
||||
@@ -218,6 +245,11 @@ impl DefaultAdminUsecase {
|
||||
Ok(pool_statuses)
|
||||
}
|
||||
|
||||
pub async fn execute_list_pools(&self) -> AdminUsecaseResult<Vec<AdminPoolListItem>> {
|
||||
let pool_statuses = self.execute_list_pool_statuses().await?;
|
||||
Ok(pool_statuses.into_iter().map(Self::pool_list_item_from_status).collect())
|
||||
}
|
||||
|
||||
pub async fn execute_query_pool_status(&self, req: QueryPoolStatusRequest) -> AdminUsecaseResult<PoolStatus> {
|
||||
let Some(endpoints) = self.endpoints() else {
|
||||
return Err(Self::app_error_default(S3ErrorCode::NotImplemented));
|
||||
@@ -246,6 +278,48 @@ impl DefaultAdminUsecase {
|
||||
store.status(idx).await.map_err(ApiError::from)
|
||||
}
|
||||
|
||||
fn pool_list_item_from_status(status: PoolStatus) -> AdminPoolListItem {
|
||||
let PoolStatus {
|
||||
id,
|
||||
cmd_line,
|
||||
last_update,
|
||||
decommission,
|
||||
} = status;
|
||||
let total_size = decommission.as_ref().map(|info| info.total_size).unwrap_or_default();
|
||||
let current_size = decommission.as_ref().map(|info| info.current_size).unwrap_or_default();
|
||||
let used_size = total_size.saturating_sub(current_size);
|
||||
|
||||
AdminPoolListItem {
|
||||
id,
|
||||
cmd_line,
|
||||
last_update,
|
||||
total_size,
|
||||
current_size,
|
||||
used_size,
|
||||
used: Self::used_ratio(total_size, used_size),
|
||||
status: Self::pool_list_status(decommission.as_ref()).to_string(),
|
||||
decommission,
|
||||
}
|
||||
}
|
||||
|
||||
fn pool_list_status(decommission: Option<&PoolDecommissionInfo>) -> &'static str {
|
||||
match decommission {
|
||||
Some(info) if info.complete => Self::POOL_STATUS_COMPLETE,
|
||||
Some(info) if info.failed => Self::POOL_STATUS_FAILED,
|
||||
Some(info) if info.canceled => Self::POOL_STATUS_CANCELED,
|
||||
Some(info) if info.start_time.is_some() => Self::POOL_STATUS_RUNNING,
|
||||
_ => Self::POOL_STATUS_ACTIVE,
|
||||
}
|
||||
}
|
||||
|
||||
fn used_ratio(total_size: usize, used_size: usize) -> f64 {
|
||||
if total_size == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
used_size as f64 / total_size as f64
|
||||
}
|
||||
|
||||
fn disk_is_online_for_readiness(disk: &Disk) -> bool {
|
||||
let state_is_acceptable = disk.state.eq_ignore_ascii_case(Self::DISK_STATE_OK)
|
||||
|| disk.state.eq_ignore_ascii_case(rustfs_madmin::ITEM_ONLINE)
|
||||
@@ -406,6 +480,8 @@ impl DefaultAdminUsecase {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rustfs_ecstore::pools::{PoolDecommissionInfo, PoolStatus};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_query_storage_info_returns_internal_error_when_store_uninitialized() {
|
||||
@@ -546,4 +622,69 @@ mod tests {
|
||||
"if any set fails write quorum, readiness must be false"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_pool_list_item_maps_capacity_and_active_status() {
|
||||
let now = OffsetDateTime::UNIX_EPOCH;
|
||||
let pool = PoolStatus {
|
||||
id: 2,
|
||||
cmd_line: "http://node{1...4}/disk{1...4}".to_string(),
|
||||
last_update: now,
|
||||
decommission: Some(PoolDecommissionInfo {
|
||||
total_size: 1_000,
|
||||
current_size: 250,
|
||||
..Default::default()
|
||||
}),
|
||||
};
|
||||
|
||||
let item = DefaultAdminUsecase::pool_list_item_from_status(pool);
|
||||
|
||||
assert_eq!(item.id, 2);
|
||||
assert_eq!(item.total_size, 1_000);
|
||||
assert_eq!(item.current_size, 250);
|
||||
assert_eq!(item.used_size, 750);
|
||||
assert!((item.used - 0.75).abs() < f64::EPSILON);
|
||||
assert_eq!(item.status, "active");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_pool_list_item_maps_running_decommission_status() {
|
||||
let pool = PoolStatus {
|
||||
id: 0,
|
||||
cmd_line: "pool-0".to_string(),
|
||||
last_update: OffsetDateTime::UNIX_EPOCH,
|
||||
decommission: Some(PoolDecommissionInfo {
|
||||
total_size: 1_000,
|
||||
current_size: 500,
|
||||
start_time: Some(OffsetDateTime::UNIX_EPOCH),
|
||||
..Default::default()
|
||||
}),
|
||||
};
|
||||
|
||||
let item = DefaultAdminUsecase::pool_list_item_from_status(pool);
|
||||
|
||||
assert_eq!(item.status, "running");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_pool_list_item_maps_terminal_decommission_statuses() {
|
||||
let complete = DefaultAdminUsecase::pool_list_status(Some(&PoolDecommissionInfo {
|
||||
complete: true,
|
||||
..Default::default()
|
||||
}));
|
||||
let failed = DefaultAdminUsecase::pool_list_status(Some(&PoolDecommissionInfo {
|
||||
failed: true,
|
||||
..Default::default()
|
||||
}));
|
||||
let canceled = DefaultAdminUsecase::pool_list_status(Some(&PoolDecommissionInfo {
|
||||
canceled: true,
|
||||
..Default::default()
|
||||
}));
|
||||
let idle = DefaultAdminUsecase::pool_list_status(None);
|
||||
|
||||
assert_eq!(complete, "complete");
|
||||
assert_eq!(failed, "failed");
|
||||
assert_eq!(canceled, "canceled");
|
||||
assert_eq!(idle, "active");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user