feat: enhance storage stats and cluster health reporting

- Introduced new data structures for bucket and storage statistics, including BucketSummary, StorageStats, and ClusterHealth.
- Implemented runtime statistics tracking for buckets, including object count and total size.
- Added methods to retrieve storage stats and bucket summaries in the FileStore.
- Enhanced the SmartStorage interface to expose storage stats and cluster health.
- Implemented tests for runtime stats, cluster health, and credential management.
- Added support for runtime-managed credentials with atomic replacement.
- Improved filesystem usage reporting for storage locations.
This commit is contained in:
2026-04-19 11:57:28 +00:00
parent c683b02e8c
commit 0e9862efca
16 changed files with 1803 additions and 85 deletions
+71 -8
View File
@@ -2,9 +2,10 @@ use hmac::{Hmac, Mac};
use hyper::body::Incoming;
use hyper::Request;
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use tokio::sync::RwLock;
use crate::config::{Credential, SmartStorageConfig};
use crate::config::{AuthConfig, Credential};
use crate::error::StorageError;
type HmacSha256 = Hmac<Sha256>;
@@ -27,7 +28,7 @@ struct SigV4Header {
/// Verify the request's SigV4 signature. Returns the caller identity on success.
pub fn verify_request(
req: &Request<Incoming>,
config: &SmartStorageConfig,
credentials: &[Credential],
) -> Result<AuthenticatedIdentity, StorageError> {
let auth_header = req
.headers()
@@ -47,7 +48,7 @@ pub fn verify_request(
let parsed = parse_auth_header(auth_header)?;
// Look up credential
let credential = find_credential(&parsed.access_key_id, config)
let credential = find_credential(&parsed.access_key_id, credentials)
.ok_or_else(StorageError::invalid_access_key_id)?;
// Get x-amz-date
@@ -163,14 +164,76 @@ fn parse_auth_header(header: &str) -> Result<SigV4Header, StorageError> {
}
/// Find a credential by access key ID.
fn find_credential<'a>(access_key_id: &str, config: &'a SmartStorageConfig) -> Option<&'a Credential> {
config
.auth
.credentials
fn find_credential<'a>(access_key_id: &str, credentials: &'a [Credential]) -> Option<&'a Credential> {
credentials
.iter()
.find(|c| c.access_key_id == access_key_id)
}
#[derive(Debug)]
pub struct RuntimeCredentialStore {
enabled: bool,
credentials: RwLock<Vec<Credential>>,
}
impl RuntimeCredentialStore {
pub fn new(config: &AuthConfig) -> Self {
Self {
enabled: config.enabled,
credentials: RwLock::new(config.credentials.clone()),
}
}
pub fn enabled(&self) -> bool {
self.enabled
}
pub async fn list_credentials(&self) -> Vec<Credential> {
self.credentials.read().await.clone()
}
pub async fn snapshot_credentials(&self) -> Vec<Credential> {
self.credentials.read().await.clone()
}
pub async fn replace_credentials(&self, credentials: Vec<Credential>) -> Result<(), StorageError> {
validate_credentials(&credentials)?;
*self.credentials.write().await = credentials;
Ok(())
}
}
fn validate_credentials(credentials: &[Credential]) -> Result<(), StorageError> {
if credentials.is_empty() {
return Err(StorageError::invalid_request(
"Credential replacement requires at least one credential.",
));
}
let mut seen_access_keys = HashSet::new();
for credential in credentials {
if credential.access_key_id.trim().is_empty() {
return Err(StorageError::invalid_request(
"Credential accessKeyId must not be empty.",
));
}
if credential.secret_access_key.trim().is_empty() {
return Err(StorageError::invalid_request(
"Credential secretAccessKey must not be empty.",
));
}
if !seen_access_keys.insert(credential.access_key_id.as_str()) {
return Err(StorageError::invalid_request(
"Credential accessKeyId values must be unique.",
));
}
}
Ok(())
}
/// Check clock skew (15 minutes max).
fn check_clock_skew(amz_date: &str) -> Result<(), StorageError> {
// Parse ISO 8601 basic format: YYYYMMDDTHHMMSSZ