feat(replication): add bandwidth-aware reporting for bucket replication metrics (#2141)

This commit is contained in:
LeonWang0735
2026-03-15 09:03:10 +08:00
committed by GitHub
parent 7f3459f5a8
commit 7f1cdaedad
14 changed files with 774 additions and 41 deletions
+49 -5
View File
@@ -24,7 +24,11 @@ use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
use rustfs_ecstore::bucket::bucket_target_sys::BucketTargetSys;
use rustfs_ecstore::bucket::metadata::BUCKET_TARGETS_FILE;
use rustfs_ecstore::bucket::metadata_sys;
use rustfs_ecstore::bucket::metadata_sys::get_replication_config;
use rustfs_ecstore::bucket::replication::BucketStats;
use rustfs_ecstore::bucket::replication::GLOBAL_REPLICATION_STATS;
use rustfs_ecstore::bucket::target::BucketTarget;
use rustfs_ecstore::error::StorageError;
use rustfs_ecstore::global::global_rustfs_port;
use rustfs_ecstore::new_object_layer_fn;
use rustfs_ecstore::store_api::{BucketOperations, BucketOptions};
@@ -32,7 +36,7 @@ use rustfs_policy::policy::action::{Action, AdminAction};
use s3s::header::CONTENT_TYPE;
use s3s::{Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, s3_error};
use std::collections::HashMap;
use tracing::{debug, error, warn};
use tracing::{debug, error, info, warn};
use url::Host;
fn extract_query_params(uri: &Uri) -> HashMap<String, String> {
@@ -98,12 +102,52 @@ pub struct GetReplicationMetricsHandler {}
#[async_trait::async_trait]
impl Operation for GetReplicationMetricsHandler {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
error!("GetReplicationMetricsHandler");
validate_replication_admin_request(&req, AdminAction::GetReplicationMetricsAction).await?;
let queries = extract_query_params(&req.uri);
if let Some(bucket) = queries.get("bucket") {
error!("get bucket:{} metrics", bucket);
let Some(bucket) = queries.get("bucket") else {
return Err(s3_error!(InvalidRequest, "bucket is required"));
};
if bucket.is_empty() {
return Err(s3_error!(InvalidRequest, "bucket is required"));
}
Ok(S3Response::new((StatusCode::OK, Body::from("Ok".to_string()))))
let Some(store) = new_object_layer_fn() else {
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
};
store
.get_bucket_info(bucket, &BucketOptions::default())
.await
.map_err(ApiError::from)?;
if let Err(err) = get_replication_config(bucket).await {
if err == StorageError::ConfigNotFound {
info!("replication configuration not found for bucket '{}'", bucket);
return Err(S3Error::with_message(
S3ErrorCode::ReplicationConfigurationNotFoundError,
"replication not found".to_string(),
));
}
error!("get_replication_config unexpected error: {:?}", err);
return Err(ApiError::from(err).into());
}
// TODO cluster cache
// In actual implementation, statistics would be obtained from cluster
// This is simplified to get from local cache
let bucket_stats = match GLOBAL_REPLICATION_STATS.get() {
Some(s) => s.get_latest_replication_stats(bucket).await,
None => BucketStats::default(),
};
let data = serde_json::to_vec(&bucket_stats.replication_stats)
.map_err(|_| S3Error::with_message(S3ErrorCode::InternalError, "serialize failed"))?;
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), headers))
}
}