mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-18 18:46:17 +00:00
refactor(app): migrate restore/select and admin info orchestration (#1917)
This commit is contained in:
@@ -15,11 +15,18 @@
|
||||
//! Admin application use-case contracts.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::app::context::AppContext;
|
||||
use crate::app::context::{AppContext, get_global_app_context};
|
||||
use crate::error::ApiError;
|
||||
use rustfs_common::data_usage::DataUsageInfo;
|
||||
use rustfs_ecstore::admin_server_info::get_server_info;
|
||||
use rustfs_madmin::InfoMessage;
|
||||
use rustfs_ecstore::data_usage::load_data_usage_from_backend;
|
||||
use rustfs_ecstore::pools::{PoolStatus, get_total_usable_capacity, get_total_usable_capacity_free};
|
||||
use rustfs_ecstore::store_api::StorageAPI;
|
||||
use rustfs_ecstore::{GLOBAL_Endpoints, new_object_layer_fn};
|
||||
use rustfs_madmin::{InfoMessage, StorageInfo};
|
||||
use s3s::S3ErrorCode;
|
||||
use std::sync::Arc;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
pub type AdminUsecaseResult<T> = Result<T, ApiError>;
|
||||
|
||||
@@ -38,30 +45,297 @@ impl std::fmt::Debug for QueryServerInfoResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub struct DependencyReadiness {
|
||||
pub storage_ready: bool,
|
||||
pub iam_ready: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct QueryPoolStatusRequest {
|
||||
pub pool: String,
|
||||
pub by_id: bool,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait AdminUsecase: Send + Sync {
|
||||
async fn query_server_info(&self, req: QueryServerInfoRequest) -> AdminUsecaseResult<QueryServerInfoResponse>;
|
||||
|
||||
async fn query_storage_info(&self) -> AdminUsecaseResult<StorageInfo>;
|
||||
|
||||
async fn query_data_usage_info(&self) -> AdminUsecaseResult<DataUsageInfo>;
|
||||
|
||||
async fn list_pool_statuses(&self) -> AdminUsecaseResult<Vec<PoolStatus>>;
|
||||
|
||||
async fn query_pool_status(&self, req: QueryPoolStatusRequest) -> AdminUsecaseResult<PoolStatus>;
|
||||
|
||||
fn collect_dependency_readiness(&self) -> DependencyReadiness;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct DefaultAdminUsecase {
|
||||
context: Arc<AppContext>,
|
||||
context: Option<Arc<AppContext>>,
|
||||
}
|
||||
|
||||
impl DefaultAdminUsecase {
|
||||
pub fn new(context: Arc<AppContext>) -> Self {
|
||||
Self { context }
|
||||
Self { context: Some(context) }
|
||||
}
|
||||
|
||||
pub fn context(&self) -> Arc<AppContext> {
|
||||
pub fn without_context() -> Self {
|
||||
Self { context: None }
|
||||
}
|
||||
|
||||
pub fn from_global() -> Self {
|
||||
Self {
|
||||
context: get_global_app_context(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context(&self) -> Option<Arc<AppContext>> {
|
||||
self.context.clone()
|
||||
}
|
||||
|
||||
fn app_error(code: S3ErrorCode, message: impl Into<String>) -> ApiError {
|
||||
ApiError {
|
||||
code,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn app_error_default(code: S3ErrorCode) -> ApiError {
|
||||
let message = ApiError::error_code_to_message(&code);
|
||||
Self::app_error(code, message)
|
||||
}
|
||||
|
||||
pub async fn execute_query_server_info(&self, req: QueryServerInfoRequest) -> AdminUsecaseResult<QueryServerInfoResponse> {
|
||||
if let Some(context) = &self.context {
|
||||
let _ = context.object_store();
|
||||
}
|
||||
|
||||
let info = get_server_info(req.include_pools).await;
|
||||
Ok(QueryServerInfoResponse { info })
|
||||
}
|
||||
|
||||
pub async fn execute_query_storage_info(&self) -> AdminUsecaseResult<StorageInfo> {
|
||||
if let Some(context) = &self.context {
|
||||
let _ = context.object_store();
|
||||
}
|
||||
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Self::app_error(S3ErrorCode::InternalError, "Not init"));
|
||||
};
|
||||
|
||||
Ok(store.storage_info().await)
|
||||
}
|
||||
|
||||
pub async fn execute_query_data_usage_info(&self) -> AdminUsecaseResult<DataUsageInfo> {
|
||||
if let Some(context) = &self.context {
|
||||
let _ = context.object_store();
|
||||
}
|
||||
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Self::app_error(S3ErrorCode::InternalError, "Not init"));
|
||||
};
|
||||
|
||||
let mut info = load_data_usage_from_backend(store.clone()).await.map_err(|e| {
|
||||
error!("load_data_usage_from_backend failed {:?}", e);
|
||||
Self::app_error(S3ErrorCode::InternalError, "load_data_usage_from_backend failed")
|
||||
})?;
|
||||
|
||||
let storage_info = store.storage_info().await;
|
||||
|
||||
// Keep the same capacity correction behavior as the previous admin handler implementation.
|
||||
const MAX_REASONABLE_CAPACITY: u64 = 100_000 * 1024 * 1024 * 1024 * 1024; // 100 PiB
|
||||
const MIN_REASONABLE_CAPACITY: u64 = 1024 * 1024 * 1024; // 1 GiB
|
||||
|
||||
let total_u64 = get_total_usable_capacity(&storage_info.disks, &storage_info) as u64;
|
||||
let free_u64 = get_total_usable_capacity_free(&storage_info.disks, &storage_info) as u64;
|
||||
|
||||
if total_u64 > MAX_REASONABLE_CAPACITY {
|
||||
error!(
|
||||
"Abnormal total capacity detected: {} bytes ({:.2} TiB), capping to physical capacity",
|
||||
total_u64,
|
||||
total_u64 as f64 / (1024.0_f64.powi(4))
|
||||
);
|
||||
|
||||
let disk_count = storage_info.disks.len();
|
||||
if disk_count > 0 {
|
||||
use std::collections::HashSet;
|
||||
let unique_disks: HashSet<String> = storage_info
|
||||
.disks
|
||||
.iter()
|
||||
.map(|disk| format!("{}|{}", disk.endpoint, disk.drive_path))
|
||||
.collect();
|
||||
|
||||
let actual_disk_count = unique_disks.len();
|
||||
|
||||
if let Some(first_disk) = storage_info.disks.first() {
|
||||
info.total_capacity = first_disk.total_space * actual_disk_count as u64;
|
||||
info.total_free_capacity = first_disk.available_space * actual_disk_count as u64;
|
||||
|
||||
info!(
|
||||
"Applied capacity correction: {} unique disks, capacity per disk: {} bytes",
|
||||
actual_disk_count, first_disk.total_space
|
||||
);
|
||||
} else {
|
||||
info.total_capacity = 0;
|
||||
info.total_free_capacity = 0;
|
||||
}
|
||||
} else {
|
||||
info.total_capacity = 0;
|
||||
info.total_free_capacity = 0;
|
||||
}
|
||||
} else if total_u64 < MIN_REASONABLE_CAPACITY && total_u64 > 0 {
|
||||
warn!(
|
||||
"Unusually small total capacity: {} bytes ({:.2} GiB)",
|
||||
total_u64,
|
||||
total_u64 as f64 / (1024.0_f64.powi(3))
|
||||
);
|
||||
info.total_capacity = total_u64;
|
||||
info.total_free_capacity = free_u64;
|
||||
} else {
|
||||
info.total_capacity = total_u64;
|
||||
info.total_free_capacity = free_u64;
|
||||
}
|
||||
|
||||
info.total_used_capacity = info.total_capacity.saturating_sub(info.total_free_capacity);
|
||||
|
||||
debug!(
|
||||
"Capacity statistics: total={:.2} TiB, free={:.2} TiB, used={:.2} TiB",
|
||||
info.total_capacity as f64 / (1024.0_f64.powi(4)),
|
||||
info.total_free_capacity as f64 / (1024.0_f64.powi(4)),
|
||||
info.total_used_capacity as f64 / (1024.0_f64.powi(4))
|
||||
);
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub async fn execute_list_pool_statuses(&self) -> AdminUsecaseResult<Vec<PoolStatus>> {
|
||||
if let Some(context) = &self.context {
|
||||
let _ = context.object_store();
|
||||
}
|
||||
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Self::app_error(S3ErrorCode::InternalError, "Not init"));
|
||||
};
|
||||
|
||||
let Some(endpoints) = GLOBAL_Endpoints.get() else {
|
||||
return Err(Self::app_error_default(S3ErrorCode::NotImplemented));
|
||||
};
|
||||
|
||||
if endpoints.legacy() {
|
||||
return Err(Self::app_error_default(S3ErrorCode::NotImplemented));
|
||||
}
|
||||
|
||||
let mut pool_statuses = Vec::new();
|
||||
for (idx, _) in endpoints.as_ref().iter().enumerate() {
|
||||
let state = store.status(idx).await.map_err(ApiError::from)?;
|
||||
pool_statuses.push(state);
|
||||
}
|
||||
|
||||
Ok(pool_statuses)
|
||||
}
|
||||
|
||||
pub async fn execute_query_pool_status(&self, req: QueryPoolStatusRequest) -> AdminUsecaseResult<PoolStatus> {
|
||||
if let Some(context) = &self.context {
|
||||
let _ = context.object_store();
|
||||
}
|
||||
|
||||
let Some(endpoints) = GLOBAL_Endpoints.get() else {
|
||||
return Err(Self::app_error_default(S3ErrorCode::NotImplemented));
|
||||
};
|
||||
|
||||
if endpoints.legacy() {
|
||||
return Err(Self::app_error_default(S3ErrorCode::NotImplemented));
|
||||
}
|
||||
|
||||
let has_idx = if req.by_id {
|
||||
let idx = req.pool.parse::<usize>().unwrap_or_default();
|
||||
if idx < endpoints.as_ref().len() { Some(idx) } else { None }
|
||||
} else {
|
||||
endpoints.get_pool_idx(&req.pool)
|
||||
};
|
||||
|
||||
let Some(idx) = has_idx else {
|
||||
warn!("specified pool {} not found, please specify a valid pool", req.pool);
|
||||
return Err(Self::app_error_default(S3ErrorCode::InvalidArgument));
|
||||
};
|
||||
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Self::app_error(S3ErrorCode::InternalError, "Not init"));
|
||||
};
|
||||
|
||||
store.status(idx).await.map_err(ApiError::from)
|
||||
}
|
||||
|
||||
pub fn execute_collect_dependency_readiness(&self) -> DependencyReadiness {
|
||||
if let Some(context) = &self.context {
|
||||
let _ = context.object_store();
|
||||
let _ = context.iam();
|
||||
}
|
||||
|
||||
DependencyReadiness {
|
||||
storage_ready: new_object_layer_fn().is_some(),
|
||||
iam_ready: rustfs_iam::get().is_ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AdminUsecase for DefaultAdminUsecase {
|
||||
async fn query_server_info(&self, req: QueryServerInfoRequest) -> AdminUsecaseResult<QueryServerInfoResponse> {
|
||||
let info = get_server_info(req.include_pools).await;
|
||||
Ok(QueryServerInfoResponse { info })
|
||||
self.execute_query_server_info(req).await
|
||||
}
|
||||
|
||||
async fn query_storage_info(&self) -> AdminUsecaseResult<StorageInfo> {
|
||||
self.execute_query_storage_info().await
|
||||
}
|
||||
|
||||
async fn query_data_usage_info(&self) -> AdminUsecaseResult<DataUsageInfo> {
|
||||
self.execute_query_data_usage_info().await
|
||||
}
|
||||
|
||||
async fn list_pool_statuses(&self) -> AdminUsecaseResult<Vec<PoolStatus>> {
|
||||
self.execute_list_pool_statuses().await
|
||||
}
|
||||
|
||||
async fn query_pool_status(&self, req: QueryPoolStatusRequest) -> AdminUsecaseResult<PoolStatus> {
|
||||
self.execute_query_pool_status(req).await
|
||||
}
|
||||
|
||||
fn collect_dependency_readiness(&self) -> DependencyReadiness {
|
||||
self.execute_collect_dependency_readiness()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_query_storage_info_returns_internal_error_when_store_uninitialized() {
|
||||
let usecase = DefaultAdminUsecase::without_context();
|
||||
|
||||
let err = usecase.execute_query_storage_info().await.unwrap_err();
|
||||
assert_eq!(err.code, S3ErrorCode::InternalError);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_query_data_usage_info_returns_internal_error_when_store_uninitialized() {
|
||||
let usecase = DefaultAdminUsecase::without_context();
|
||||
|
||||
let err = usecase.execute_query_data_usage_info().await.unwrap_err();
|
||||
assert_eq!(err.code, S3ErrorCode::InternalError);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_collect_dependency_readiness_returns_state_flags() {
|
||||
let usecase = DefaultAdminUsecase::without_context();
|
||||
|
||||
let readiness = usecase.execute_collect_dependency_readiness();
|
||||
let _ = readiness.storage_ready;
|
||||
let _ = readiness.iam_ready;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user