diff --git a/.docker/observability/grafana/dashboards/rustfs.json b/.docker/observability/grafana/dashboards/rustfs.json
index 315de3c69..86f595180 100644
--- a/.docker/observability/grafana/dashboards/rustfs.json
+++ b/.docker/observability/grafana/dashboards/rustfs.json
@@ -9037,13 +9037,13 @@
"uid": "${datasource}"
},
"editorMode": "code",
- "expr": "{__name__=~\"rustfs_(notification|audit)_.*\",job=~\"$job\"}",
+ "expr": "{__name__=~\"rustfs_(notification|audit|log_chain)_.*\",job=~\"$job\"}",
"legendFormat": "{{__name__}}",
"range": true,
"refId": "A"
}
],
- "title": "Notification / Audit (All)",
+ "title": "Notification / Audit / LogChain (All)",
"type": "timeseries"
},
{
diff --git a/.docker/observability/prometheus-rules/rustfs-dashboard.yml b/.docker/observability/prometheus-rules/rustfs-dashboard.yml
index 898b82b2a..328df017e 100644
--- a/.docker/observability/prometheus-rules/rustfs-dashboard.yml
+++ b/.docker/observability/prometheus-rules/rustfs-dashboard.yml
@@ -38,3 +38,16 @@ groups:
expr: sum by (job) (rate(rustfs_scanner_buckets_scanned_total[5m]))
- record: rustfs:scanner_cycles_success:rate5m
expr: sum by (job) (rate(rustfs_scanner_cycles_total{result="success"}[5m]))
+
+ - record: rustfs:log_chain_op_event_mismatch:rate5m
+ expr: sum by (job) (rate(rustfs_log_chain_op_event_mismatch_total[5m]))
+
+ - alert: RustFSLogChainOpEventMismatchDetected
+ expr: rustfs:log_chain_op_event_mismatch:rate5m > 0
+ for: 10m
+ labels:
+ severity: warning
+ component: s3-log-chain
+ annotations:
+ summary: "RustFS log-chain op/event mismatch detected"
+ description: "job={{ $labels.job }} has non-zero rustfs_log_chain_op_event_mismatch_total rate for more than 10m. Check s3 op/event mapping changes."
diff --git a/Cargo.lock b/Cargo.lock
index 1c4331f5b..b5e4de12d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3698,6 +3698,7 @@ dependencies = [
"russh-sftp",
"rustfs-common",
"rustfs-config",
+ "rustfs-data-usage",
"rustfs-ecstore",
"rustfs-filemeta",
"rustfs-lock",
@@ -9315,13 +9316,13 @@ dependencies = [
"rmp-serde",
"rsa 0.10.0-rc.18",
"rust-embed",
- "rustfs-appauth",
"rustfs-audit",
"rustfs-common",
"rustfs-concurrency",
"rustfs-config",
"rustfs-credentials",
"rustfs-crypto",
+ "rustfs-data-usage",
"rustfs-ecstore",
"rustfs-filemeta",
"rustfs-heal",
@@ -9339,7 +9340,8 @@ dependencies = [
"rustfs-protocols",
"rustfs-protos",
"rustfs-rio",
- "rustfs-s3-common",
+ "rustfs-s3-ops",
+ "rustfs-s3-types",
"rustfs-s3select-api",
"rustfs-s3select-query",
"rustfs-scanner",
@@ -9381,17 +9383,6 @@ dependencies = [
"zip",
]
-[[package]]
-name = "rustfs-appauth"
-version = "1.0.0-beta.3"
-dependencies = [
- "base64-simd",
- "rand 0.10.1",
- "rsa 0.10.0-rc.18",
- "serde",
- "serde_json",
-]
-
[[package]]
name = "rustfs-audit"
version = "1.0.0-beta.3"
@@ -9404,7 +9395,8 @@ dependencies = [
"metrics",
"rustfs-config",
"rustfs-ecstore",
- "rustfs-s3-common",
+ "rustfs-s3-ops",
+ "rustfs-s3-types",
"rustfs-targets",
"serde",
"serde_json",
@@ -9433,13 +9425,9 @@ dependencies = [
name = "rustfs-common"
version = "1.0.0-beta.3"
dependencies = [
- "async-trait",
"chrono",
"metrics",
- "path-clean",
"rmp-serde",
- "rustfs-filemeta",
- "rustfs-madmin",
"s3s",
"serde",
"tokio",
@@ -9484,10 +9472,13 @@ version = "1.0.0-beta.3"
dependencies = [
"aes-gcm 0.11.0-rc.3",
"argon2 0.6.0-rc.8",
+ "base64-simd",
"chacha20poly1305",
"jsonwebtoken",
"pbkdf2 0.13.0",
"rand 0.10.1",
+ "rsa 0.10.0-rc.18",
+ "serde",
"serde_json",
"sha2 0.11.0",
"test-case",
@@ -9495,6 +9486,17 @@ dependencies = [
"time",
]
+[[package]]
+name = "rustfs-data-usage"
+version = "1.0.0-beta.3"
+dependencies = [
+ "async-trait",
+ "path-clean",
+ "rmp-serde",
+ "rustfs-filemeta",
+ "serde",
+]
+
[[package]]
name = "rustfs-ecstore"
version = "1.0.0-beta.3"
@@ -9553,20 +9555,23 @@ dependencies = [
"rmp-serde",
"rustfs-checksums",
"rustfs-common",
+ "rustfs-concurrency",
"rustfs-config",
"rustfs-credentials",
+ "rustfs-data-usage",
"rustfs-filemeta",
"rustfs-io-metrics",
"rustfs-kms",
"rustfs-lock",
"rustfs-madmin",
+ "rustfs-object-capacity",
"rustfs-policy",
"rustfs-protos",
"rustfs-rio",
- "rustfs-s3-common",
+ "rustfs-s3-ops",
+ "rustfs-s3-types",
"rustfs-signer",
"rustfs-utils",
- "rustfs-workers",
"rustix 1.1.4",
"rustls",
"s3s",
@@ -9700,6 +9705,7 @@ dependencies = [
"criterion",
"metrics",
"num_cpus",
+ "rustfs-s3-ops",
"sysinfo",
"thiserror 2.0.18",
"tokio",
@@ -9860,7 +9866,8 @@ dependencies = [
"rustc-hash",
"rustfs-config",
"rustfs-ecstore",
- "rustfs-s3-common",
+ "rustfs-s3-ops",
+ "rustfs-s3-types",
"rustfs-targets",
"rustfs-utils",
"serde",
@@ -9881,7 +9888,6 @@ version = "1.0.0-beta.3"
dependencies = [
"criterion",
"futures",
- "rustfs-common",
"rustfs-config",
"rustfs-io-metrics",
"rustfs-utils",
@@ -10034,6 +10040,7 @@ dependencies = [
"prost 0.14.3",
"rustfs-common",
"rustfs-config",
+ "rustfs-io-metrics",
"rustfs-utils",
"tonic",
"tonic-prost",
@@ -10061,6 +10068,7 @@ dependencies = [
"reqwest",
"rustfs-common",
"rustfs-config",
+ "rustfs-io-metrics",
"rustfs-utils",
"s3s",
"serde",
@@ -10075,10 +10083,16 @@ dependencies = [
]
[[package]]
-name = "rustfs-s3-common"
+name = "rustfs-s3-ops"
+version = "1.0.0-beta.3"
+dependencies = [
+ "rustfs-s3-types",
+]
+
+[[package]]
+name = "rustfs-s3-types"
version = "1.0.0-beta.3"
dependencies = [
- "metrics",
"serde",
"serde_json",
]
@@ -10141,6 +10155,7 @@ dependencies = [
"rmp-serde",
"rustfs-common",
"rustfs-config",
+ "rustfs-data-usage",
"rustfs-ecstore",
"rustfs-filemeta",
"rustfs-utils",
@@ -10195,7 +10210,8 @@ dependencies = [
"rustfs-config",
"rustfs-ecstore",
"rustfs-kafka-async",
- "rustfs-s3-common",
+ "rustfs-s3-ops",
+ "rustfs-s3-types",
"rustfs-utils",
"rustls",
"rustls-native-certs",
@@ -10284,14 +10300,6 @@ dependencies = [
"zstd",
]
-[[package]]
-name = "rustfs-workers"
-version = "1.0.0-beta.3"
-dependencies = [
- "tokio",
- "tracing",
-]
-
[[package]]
name = "rustfs-zip"
version = "1.0.0-beta.3"
diff --git a/Cargo.toml b/Cargo.toml
index d2c18d0c8..385136776 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,10 +15,10 @@
[workspace]
members = [
"rustfs", # Core file system implementation
- "crates/appauth", # Application authentication and authorization
"crates/audit", # Audit target management system with multi-target fan-out
"crates/checksums", # client checksums
"crates/common", # Shared utilities and data structures
+ "crates/data-usage", # Shared data usage models and algorithms
"crates/config", # Configuration management
"crates/credentials", # Credential management system
"crates/crypto", # Cryptography and security features
@@ -39,7 +39,8 @@ members = [
"crates/protos", # Protocol buffer definitions
"crates/rio", # Rust I/O utilities and abstractions
"crates/concurrency", # Concurrency management for RustFS - timeout, locking, backpressure, and I/O scheduling
- "crates/s3-common", # Common utilities and data structures for S3 compatibility
+ "crates/s3-types", # S3 event type definitions
+ "crates/s3-ops", # S3 operation definitions and mapping
"crates/s3select-api", # S3 Select API interface
"crates/s3select-query", # S3 Select query engine
"crates/scanner", # Scanner for data integrity checks and health monitoring
@@ -47,7 +48,6 @@ members = [
"crates/targets", # Target-specific configurations and utilities
"crates/trusted-proxies", # Trusted proxies management
"crates/utils", # Utility functions and helpers
- "crates/workers", # Worker thread pools and task scheduling
"crates/io-metrics", # Zero-copy metrics collection for performance analysis
"crates/io-core", # Zero-copy core reader and writer implementations
"crates/zip", # ZIP file handling and compression
@@ -78,10 +78,10 @@ redundant_clone = "warn"
# RustFS Internal Crates
rustfs = { path = "./rustfs", version = "1.0.0-beta.3" }
rustfs-heal = { path = "crates/heal", version = "1.0.0-beta.3" }
-rustfs-appauth = { path = "crates/appauth", version = "1.0.0-beta.3" }
rustfs-audit = { path = "crates/audit", version = "1.0.0-beta.3" }
rustfs-checksums = { path = "crates/checksums", version = "1.0.0-beta.3" }
rustfs-common = { path = "crates/common", version = "1.0.0-beta.3" }
+rustfs-data-usage = { path = "crates/data-usage", version = "1.0.0-beta.3" }
rustfs-config = { path = "./crates/config", version = "1.0.0-beta.3" }
rustfs-concurrency = { path = "./crates/concurrency", version = "1.0.0-beta.3" }
rustfs-credentials = { path = "crates/credentials", version = "1.0.0-beta.3" }
@@ -102,7 +102,8 @@ rustfs-policy = { path = "crates/policy", version = "1.0.0-beta.3" }
rustfs-protos = { path = "crates/protos", version = "1.0.0-beta.3" }
rustfs-protocols = { path = "crates/protocols", version = "1.0.0-beta.3" }
rustfs-rio = { path = "crates/rio", version = "1.0.0-beta.3" }
-rustfs-s3-common = { path = "crates/s3-common", version = "1.0.0-beta.3" }
+rustfs-s3-types = { path = "crates/s3-types", version = "1.0.0-beta.3" }
+rustfs-s3-ops = { path = "crates/s3-ops", version = "1.0.0-beta.3" }
rustfs-s3select-api = { path = "crates/s3select-api", version = "1.0.0-beta.3" }
rustfs-s3select-query = { path = "crates/s3select-query", version = "1.0.0-beta.3" }
rustfs-scanner = { path = "crates/scanner", version = "1.0.0-beta.3" }
@@ -110,7 +111,6 @@ rustfs-signer = { path = "crates/signer", version = "1.0.0-beta.3" }
rustfs-trusted-proxies = { path = "crates/trusted-proxies", version = "1.0.0-beta.3" }
rustfs-targets = { path = "crates/targets", version = "1.0.0-beta.3" }
rustfs-utils = { path = "crates/utils", version = "1.0.0-beta.3" }
-rustfs-workers = { path = "crates/workers", version = "1.0.0-beta.3" }
rustfs-zip = { path = "./crates/zip", version = "1.0.0-beta.3" }
# Async Runtime and Networking
@@ -202,7 +202,6 @@ aws-credential-types = { version = "1.2.14" }
aws-sdk-s3 = { version = "1.132.0", default-features = false, features = ["sigv4a", "default-https-client", "rt-tokio"] }
aws-smithy-http-client = { version = "1.1.12", default-features = false, features = ["default-client", "rustls-aws-lc"] }
aws-smithy-types = { version = "1.4.7" }
-backtrace = "0.3.76"
base64 = "0.22.1"
base64-simd = "0.8.0"
brotli = "8.0.2"
diff --git a/crates/appauth/README.md b/crates/appauth/README.md
deleted file mode 100644
index 43f4b9654..000000000
--- a/crates/appauth/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-[](https://rustfs.com)
-
-# RustFS AppAuth - Application Authentication
-
-
- Application-level authentication and authorization module for RustFS distributed object storage
-
-
-
-
- ๐ Documentation
- ยท ๐ Bug Reports
- ยท ๐ฌ Discussions
-
-
----
-
-## ๐ Overview
-
-**RustFS AppAuth** provides application-level authentication and authorization capabilities for the [RustFS](https://rustfs.com) distributed object storage system. For the complete RustFS experience, please visit the [main RustFS repository](https://github.com/rustfs/rustfs).
-
-## โจ Features
-
-- JWT-based authentication with secure token management
-- RBAC (Role-Based Access Control) for fine-grained permissions
-- Multi-tenant application isolation and management
-- OAuth 2.0 and OpenID Connect integration
-- API key management and rotation
-- Session management with configurable expiration
-
-## ๐ Documentation
-
-For comprehensive documentation, examples, and usage guides, please visit the main [RustFS repository](https://github.com/rustfs/rustfs).
-
-## ๐ License
-
-This project is licensed under the Apache License 2.0 - see the [LICENSE](../../LICENSE) file for details.
diff --git a/crates/audit/Cargo.toml b/crates/audit/Cargo.toml
index 04ce7cda6..f58ae10ef 100644
--- a/crates/audit/Cargo.toml
+++ b/crates/audit/Cargo.toml
@@ -29,7 +29,8 @@ categories = ["web-programming", "development-tools", "asynchronous", "api-bindi
rustfs-targets = { workspace = true }
rustfs-config = { workspace = true, features = ["audit", "constants"] }
rustfs-ecstore = { workspace = true }
-rustfs-s3-common = { workspace = true }
+rustfs-s3-types = { workspace = true }
+rustfs-s3-ops = { workspace = true }
chrono = { workspace = true }
const-str = { workspace = true }
futures = { workspace = true }
diff --git a/crates/audit/src/entity.rs b/crates/audit/src/entity.rs
index 3e2375f5e..39931ecfb 100644
--- a/crates/audit/src/entity.rs
+++ b/crates/audit/src/entity.rs
@@ -14,7 +14,7 @@
use chrono::{DateTime, Utc};
use hashbrown::HashMap;
-use rustfs_s3_common::EventName;
+use rustfs_s3_types::EventName;
use serde::{Deserialize, Serialize};
use serde_json::Value;
diff --git a/crates/checksums/Cargo.toml b/crates/checksums/Cargo.toml
index 8d6fe9dc1..6ac09669b 100644
--- a/crates/checksums/Cargo.toml
+++ b/crates/checksums/Cargo.toml
@@ -23,7 +23,7 @@ homepage.workspace = true
description = "Checksum calculation and verification callbacks for HTTP request and response bodies sent by service clients generated by RustFS, ensuring data integrity and authenticity."
keywords = ["checksum-calculation", "verification", "integrity", "authenticity", "rustfs"]
categories = ["web-programming", "development-tools", "network-programming"]
-documentation = "https://docs.rs/rustfs-signer/latest/rustfs_checksum/"
+documentation = "https://docs.rs/rustfs-checksums/latest/rustfs_checksum/"
[dependencies]
bytes = { workspace = true }
diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml
index 4886ce906..c86ba3ea3 100644
--- a/crates/common/Cargo.toml
+++ b/crates/common/Cargo.toml
@@ -33,12 +33,8 @@ tonic = { workspace = true }
uuid = { workspace = true }
chrono = { workspace = true }
metrics = { workspace = true }
-rustfs-madmin = { workspace = true }
-rustfs-filemeta = { workspace = true }
serde = { workspace = true }
-path-clean = { workspace = true }
rmp-serde = { workspace = true }
-async-trait = { workspace = true }
s3s = { workspace = true }
tracing = { workspace = true }
diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs
index df06c5966..bab6931e8 100644
--- a/crates/common/src/lib.rs
+++ b/crates/common/src/lib.rs
@@ -13,12 +13,9 @@
// limitations under the License.
pub mod bucket_stats;
-pub mod capacity_scope;
// pub mod error;
-pub mod data_usage;
pub mod globals;
pub mod heal_channel;
-pub mod internode_metrics;
pub mod last_minute;
pub mod metrics;
mod readiness;
diff --git a/crates/common/src/metrics.rs b/crates/common/src/metrics.rs
index 3b860ab63..744435ccd 100644
--- a/crates/common/src/metrics.rs
+++ b/crates/common/src/metrics.rs
@@ -14,7 +14,6 @@
use crate::last_minute::{AccElem, LastMinuteLatency};
use chrono::{DateTime, Utc};
-use rustfs_madmin::metrics::{ScannerMetrics as M_ScannerMetrics, TimedAction};
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
@@ -351,6 +350,32 @@ pub struct CurrentCycle {
pub started: DateTime,
}
+#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
+pub struct ScannerTimedAction {
+ pub count: u64,
+ pub acc_time: u64,
+ pub bytes: u64,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize)]
+pub struct ScannerLastMinute {
+ pub actions: HashMap,
+ pub ilm: HashMap,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize)]
+pub struct ScannerMetricsReport {
+ pub collected_at: DateTime,
+ pub current_cycle: u64,
+ pub current_started: DateTime,
+ pub cycles_completed_at: Vec>,
+ pub ongoing_buckets: usize,
+ pub life_time_ops: HashMap,
+ pub life_time_ilm: HashMap,
+ pub last_minute: ScannerLastMinute,
+ pub active_paths: Vec,
+}
+
impl CurrentCycle {
pub fn unmarshal(&mut self, buf: &[u8]) -> Result<(), Box> {
*self = rmp_serde::from_slice(buf)?;
@@ -572,8 +597,8 @@ impl Metrics {
}
/// Build a full metrics report snapshot.
- pub async fn report(&self) -> M_ScannerMetrics {
- let mut m = M_ScannerMetrics::default();
+ pub async fn report(&self) -> ScannerMetricsReport {
+ let mut m = ScannerMetricsReport::default();
if let Some(cycle) = self.get_cycle().await {
m.current_cycle = cycle.current;
@@ -606,7 +631,7 @@ impl Metrics {
{
m.last_minute.actions.insert(
metric.as_str().to_string(),
- TimedAction {
+ ScannerTimedAction {
count: last_min.n,
acc_time: last_min.total,
bytes: last_min.size,
@@ -633,7 +658,7 @@ impl Metrics {
{
m.last_minute.ilm.insert(
action.as_str().to_string(),
- TimedAction {
+ ScannerTimedAction {
count: last_min.n,
acc_time: last_min.total,
bytes: last_min.size,
diff --git a/crates/concurrency/src/lib.rs b/crates/concurrency/src/lib.rs
index d7dbb0344..6d0b5ff8e 100644
--- a/crates/concurrency/src/lib.rs
+++ b/crates/concurrency/src/lib.rs
@@ -124,6 +124,8 @@ mod backpressure;
#[cfg(feature = "scheduler")]
mod scheduler;
+pub mod workers;
+
// Public module exports with feature gates
#[cfg(feature = "timeout")]
pub use timeout::{TimeoutConfig, TimeoutGuard, TimeoutManager};
diff --git a/crates/workers/src/workers.rs b/crates/concurrency/src/workers.rs
similarity index 86%
rename from crates/workers/src/workers.rs
rename to crates/concurrency/src/workers.rs
index 3b56444c0..0c116a9bd 100644
--- a/crates/workers/src/workers.rs
+++ b/crates/concurrency/src/workers.rs
@@ -12,10 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+//! Worker slot limiter used by long-running background workflows.
+
use std::sync::Arc;
use tokio::sync::{Mutex, Notify};
use tracing::info;
+/// Cooperative worker-slot controller for async tasks.
pub struct Workers {
available: Mutex, // Available working slots
notify: Notify, // Used to notify waiting tasks
@@ -23,20 +26,20 @@ pub struct Workers {
}
impl Workers {
- // Create a Workers object that allows up to n jobs to execute concurrently
- pub fn new(n: usize) -> Result, &'static str> {
+ /// Create a [`Workers`] object that allows up to `n` jobs to execute concurrently.
+ pub fn new(n: usize) -> Result, &'static str> {
if n == 0 {
return Err("n must be > 0");
}
- Ok(Arc::new(Workers {
+ Ok(Arc::new(Self {
available: Mutex::new(n),
notify: Notify::new(),
limit: n,
}))
}
- // Give a job a chance to be executed
+ /// Acquire a worker slot, waiting until one becomes available.
pub async fn take(&self) {
loop {
let mut available = self.available.lock().await;
@@ -51,7 +54,7 @@ impl Workers {
}
}
- // Release a job's slot
+ /// Release a worker slot.
pub async fn give(&self) {
let mut available = self.available.lock().await;
info!("worker give, {}", *available);
@@ -59,7 +62,7 @@ impl Workers {
self.notify.notify_one(); // Notify a waiting task
}
- // Wait for all concurrent jobs to complete
+ /// Wait until all worker slots are released.
pub async fn wait(&self) {
loop {
{
@@ -74,6 +77,7 @@ impl Workers {
info!("worker wait end");
}
+ /// Return the current number of available worker slots.
pub async fn available(&self) -> usize {
*self.available.lock().await
}
diff --git a/crates/crypto/Cargo.toml b/crates/crypto/Cargo.toml
index f1e2b4772..651066731 100644
--- a/crates/crypto/Cargo.toml
+++ b/crates/crypto/Cargo.toml
@@ -33,8 +33,11 @@ aes-gcm = { workspace = true, optional = true }
argon2 = { workspace = true, optional = true }
chacha20poly1305 = { workspace = true, optional = true }
jsonwebtoken = { workspace = true }
+base64-simd = { workspace = true }
pbkdf2 = { workspace = true, optional = true }
rand = { workspace = true, optional = true }
+rsa = { workspace = true, features = ["sha2"] }
+serde = { workspace = true, features = ["derive"] }
sha2 = { workspace = true, optional = true }
thiserror.workspace = true
serde_json.workspace = true
diff --git a/crates/crypto/src/lib.rs b/crates/crypto/src/lib.rs
index 8bed5bfd8..e66c7484c 100644
--- a/crates/crypto/src/lib.rs
+++ b/crates/crypto/src/lib.rs
@@ -16,6 +16,7 @@
mod encdec;
mod error;
mod jwt;
+pub mod license_token;
pub use encdec::decrypt::decrypt_data;
pub use encdec::encrypt::encrypt_data;
@@ -25,3 +26,4 @@ pub use encdec::stream_io::{decrypt_stream_io, encrypt_stream_io};
pub use error::Error;
pub use jwt::decode::decode as jwt_decode;
pub use jwt::encode::encode as jwt_encode;
+pub use license_token::{Token, parse_license_with_public_key, parse_signed_license_token, sign_license_token};
diff --git a/crates/appauth/src/token.rs b/crates/crypto/src/license_token.rs
similarity index 81%
rename from crates/appauth/src/token.rs
rename to crates/crypto/src/license_token.rs
index 22952a7d2..8128605ec 100644
--- a/crates/appauth/src/token.rs
+++ b/crates/crypto/src/license_token.rs
@@ -115,12 +115,20 @@ mod tests {
let private_key = RsaPrivateKey::new(&mut rng, bits).expect("Failed to generate private key");
let public_key = RsaPublicKey::from(&private_key);
- let private_key_pem = private_key.to_pkcs8_pem(LineEnding::LF).unwrap();
- let public_key_pem = public_key.to_public_key_pem(LineEnding::LF).unwrap();
+ let private_key_pem = private_key
+ .to_pkcs8_pem(LineEnding::LF)
+ .expect("failed to encode private key pem");
+ let public_key_pem = public_key
+ .to_public_key_pem(LineEnding::LF)
+ .expect("failed to encode public key pem");
let token = Token {
name: "test_app".to_string(),
- expired: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 3600, // 1 hour from now
+ expired: SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("system clock before unix epoch")
+ .as_secs()
+ + 3600, // 1 hour from now
};
let encoded = sign_license_token(&token, &private_key_pem).expect("Failed to encode token");
@@ -139,12 +147,20 @@ mod tests {
let private_key = RsaPrivateKey::new(&mut rng, bits).expect("Failed to generate private key");
let public_key = RsaPublicKey::from(&private_key);
- let private_key_pem = private_key.to_pkcs8_pem(LineEnding::LF).unwrap();
- let public_key_pem = public_key.to_public_key_pem(LineEnding::LF).unwrap();
+ let private_key_pem = private_key
+ .to_pkcs8_pem(LineEnding::LF)
+ .expect("failed to encode private key pem");
+ let public_key_pem = public_key
+ .to_public_key_pem(LineEnding::LF)
+ .expect("failed to encode public key pem");
let token = Token {
name: "test_app".to_string(),
- expired: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 3600,
+ expired: SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("system clock before unix epoch")
+ .as_secs()
+ + 3600,
};
let encoded = gencode(&token, &public_key_pem).expect("Failed to encode token");
@@ -159,11 +175,19 @@ mod tests {
let mut rng = rand::rng();
let private_key = RsaPrivateKey::new(&mut rng, 2048).expect("Failed to generate private key");
let public_key = RsaPublicKey::from(&private_key);
- let private_key_pem = private_key.to_pkcs8_pem(LineEnding::LF).unwrap();
- let public_key_pem = public_key.to_public_key_pem(LineEnding::LF).unwrap();
+ let private_key_pem = private_key
+ .to_pkcs8_pem(LineEnding::LF)
+ .expect("failed to encode private key pem");
+ let public_key_pem = public_key
+ .to_public_key_pem(LineEnding::LF)
+ .expect("failed to encode public key pem");
let token = Token {
name: "test_app".to_string(),
- expired: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 3600,
+ expired: SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("system clock before unix epoch")
+ .as_secs()
+ + 3600,
};
let encoded = sign_license_token(&token, &private_key_pem).expect("Failed to encode token");
@@ -181,7 +205,7 @@ mod tests {
#[test]
fn test_source_does_not_embed_private_key() {
- let source = include_str!("token.rs");
+ let source = include_str!("license_token.rs");
let forbidden = ["BEGIN", "PRIVATE KEY"].join(" ");
assert!(!source.contains(&forbidden));
@@ -192,7 +216,9 @@ mod tests {
let mut rng = rand::rng();
let private_key = RsaPrivateKey::new(&mut rng, 2048).expect("Failed to generate private key");
let public_key = RsaPublicKey::from(&private_key);
- let public_key_pem = public_key.to_public_key_pem(LineEnding::LF).unwrap();
+ let public_key_pem = public_key
+ .to_public_key_pem(LineEnding::LF)
+ .expect("failed to encode public key pem");
let invalid_token = "invalid_base64_token";
let result = parse_signed_license_token(invalid_token, &public_key_pem);
@@ -204,7 +230,11 @@ mod tests {
fn test_sign_license_token_with_invalid_signing_key() {
let token = Token {
name: "test_app".to_string(),
- expired: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 3600, // 1 hour from now
+ expired: SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("system clock before unix epoch")
+ .as_secs()
+ + 3600, // 1 hour from now
};
let invalid_key = "invalid_private_key";
diff --git a/crates/workers/Cargo.toml b/crates/data-usage/Cargo.toml
similarity index 65%
rename from crates/workers/Cargo.toml
rename to crates/data-usage/Cargo.toml
index 70a360ef9..d6cd797ae 100644
--- a/crates/workers/Cargo.toml
+++ b/crates/data-usage/Cargo.toml
@@ -13,24 +13,26 @@
# limitations under the License.
[package]
-name = "rustfs-workers"
+name = "rustfs-data-usage"
+version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
-version.workspace = true
homepage.workspace = true
-description = "Workers for RustFS, providing background processing capabilities for tasks such as data synchronization, indexing, and more."
-keywords = ["workers", "tasks", "rustfs", "Minio"]
-categories = ["web-programming", "development-tools"]
-documentation = "https://docs.rs/rustfs-workers/latest/rustfs_workers/"
+description = "Shared data usage models and algorithms for RustFS"
+keywords = ["rustfs", "data-usage", "cache", "histogram"]
+categories = ["data-structures", "filesystem"]
[lints]
workspace = true
[dependencies]
-tokio = { workspace = true, features = ["sync", "time", "macros"] }
-tracing.workspace = true
+serde = { workspace = true }
+path-clean = { workspace = true }
+rmp-serde = { workspace = true }
+async-trait = { workspace = true }
+rustfs-filemeta = { workspace = true }
[lib]
doctest = false
diff --git a/crates/common/src/data_usage.rs b/crates/data-usage/src/data_usage.rs
similarity index 98%
rename from crates/common/src/data_usage.rs
rename to crates/data-usage/src/data_usage.rs
index 21a8f9932..9071ff81e 100644
--- a/crates/common/src/data_usage.rs
+++ b/crates/data-usage/src/data_usage.rs
@@ -312,6 +312,12 @@ impl SizeHistogram {
}
res
}
+
+ pub fn merge_from(&mut self, other: &Self) {
+ for (dst, src) in self.0.iter_mut().zip(other.0.iter()) {
+ *dst += src;
+ }
+ }
}
/// Versions histogram for version count distribution
@@ -361,6 +367,12 @@ impl VersionsHistogram {
}
res
}
+
+ pub fn merge_from(&mut self, other: &Self) {
+ for (dst, src) in self.0.iter_mut().zip(other.0.iter()) {
+ *dst += src;
+ }
+ }
}
/// Replication statistics for a single target
@@ -419,6 +431,9 @@ pub struct DataUsageEntry {
pub obj_versions: VersionsHistogram,
pub replication_stats: Option,
pub compacted: bool,
+ /// Number of objects that failed to scan (e.g., IO errors)
+ #[serde(default)]
+ pub failed_objects: usize,
}
impl DataUsageEntry {
@@ -456,6 +471,7 @@ impl DataUsageEntry {
self.versions += other.versions;
self.delete_markers += other.delete_markers;
self.size += other.size;
+ self.failed_objects += other.failed_objects;
if let Some(o_rep) = &other.replication_stats {
let s_rep = self.replication_stats.get_or_insert_with(ReplicationAllStats::default);
@@ -490,9 +506,11 @@ impl DataUsageEntry {
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct DataUsageCacheInfo {
pub name: String,
- pub next_cycle: u32,
+ pub next_cycle: u64,
pub last_update: Option,
pub skip_healing: bool,
+ #[serde(default)]
+ pub failed_objects: HashMap,
}
/// Data usage cache
diff --git a/crates/workers/src/lib.rs b/crates/data-usage/src/lib.rs
similarity index 93%
rename from crates/workers/src/lib.rs
rename to crates/data-usage/src/lib.rs
index 1512b3655..6a67b6f88 100644
--- a/crates/workers/src/lib.rs
+++ b/crates/data-usage/src/lib.rs
@@ -12,4 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-pub mod workers;
+pub mod data_usage;
+
+pub use data_usage::*;
diff --git a/crates/e2e_test/Cargo.toml b/crates/e2e_test/Cargo.toml
index e978e0a95..c410a0009 100644
--- a/crates/e2e_test/Cargo.toml
+++ b/crates/e2e_test/Cargo.toml
@@ -32,6 +32,7 @@ sftp = []
rustfs-config = { workspace = true, features = ["constants"] }
rustfs-ecstore.workspace = true
rustfs-common.workspace = true
+rustfs-data-usage.workspace = true
rustfs-rio.workspace = true
flatbuffers.workspace = true
futures.workspace = true
diff --git a/crates/e2e_test/src/data_usage_test.rs b/crates/e2e_test/src/data_usage_test.rs
index 1121b366c..a64168397 100644
--- a/crates/e2e_test/src/data_usage_test.rs
+++ b/crates/e2e_test/src/data_usage_test.rs
@@ -13,7 +13,7 @@
// limitations under the License.
use aws_sdk_s3::primitives::ByteStream;
-use rustfs_common::data_usage::DataUsageInfo;
+use rustfs_data_usage::DataUsageInfo;
use serial_test::serial;
use crate::common::{RustFSTestEnvironment, TEST_BUCKET, awscurl_get, init_logging};
diff --git a/crates/ecstore/Cargo.toml b/crates/ecstore/Cargo.toml
index c91fa821d..7e8ec1312 100644
--- a/crates/ecstore/Cargo.toml
+++ b/crates/ecstore/Cargo.toml
@@ -45,7 +45,10 @@ rustfs-common.workspace = true
rustfs-policy.workspace = true
rustfs-protos.workspace = true
rustfs-kms.workspace = true
-rustfs-s3-common = { workspace = true }
+rustfs-s3-types = { workspace = true }
+rustfs-s3-ops = { workspace = true }
+rustfs-data-usage.workspace = true
+rustfs-object-capacity.workspace = true
async-trait.workspace = true
bytes.workspace = true
byteorder = { workspace = true }
@@ -103,7 +106,7 @@ memmap2 = { workspace = true }
libc.workspace = true
rustix = { workspace = true }
rustfs-madmin.workspace = true
-rustfs-workers.workspace = true
+rustfs-concurrency.workspace = true
reqwest = { workspace = true }
aes-gcm.workspace = true
aws-sdk-s3 = { workspace = true }
diff --git a/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs b/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs
index 8b41f25ed..9ea0947d9 100644
--- a/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs
+++ b/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs
@@ -44,7 +44,6 @@ use async_channel::{Receiver as A_Receiver, Sender as A_Sender, bounded};
use futures::Future;
use http::HeaderMap;
use lazy_static::lazy_static;
-use rustfs_common::data_usage::TierStats;
use rustfs_common::heal_channel::rep_has_active_rules;
use rustfs_common::metrics::{IlmAction, Metrics};
use rustfs_config::{
@@ -52,11 +51,12 @@ use rustfs_config::{
DEFAULT_TRANSITION_WORKERS_CAP, ENV_TEST_FORCE_IMMEDIATE_TRANSITION_ENQUEUE_TIMEOUT, ENV_TRANSITION_QUEUE_CAPACITY,
ENV_TRANSITION_QUEUE_SEND_TIMEOUT_MS, ENV_TRANSITION_WORKERS, ENV_TRANSITION_WORKERS_ABSOLUTE_MAX,
};
+use rustfs_data_usage::TierStats;
use rustfs_filemeta::{
FileInfo, FileInfoOpts, NULL_VERSION_ID, REPLICATE_INCOMING_DELETE, ReplicateDecision, ReplicationState, RestoreStatusOps,
VersionPurgeStatusType, get_file_info, is_restored_object_on_disk,
};
-use rustfs_s3_common::EventName;
+use rustfs_s3_types::EventName;
use rustfs_utils::{get_env_i64, get_env_usize, path::encode_dir_object, string::strings_has_prefix_fold};
use s3s::dto::{
BucketLifecycleConfiguration, DefaultRetention, ExpirationStatus, ReplicationConfiguration, RestoreRequest,
diff --git a/crates/ecstore/src/bucket/lifecycle/tier_last_day_stats.rs b/crates/ecstore/src/bucket/lifecycle/tier_last_day_stats.rs
index cd2fcb36d..b32fb32f5 100644
--- a/crates/ecstore/src/bucket/lifecycle/tier_last_day_stats.rs
+++ b/crates/ecstore/src/bucket/lifecycle/tier_last_day_stats.rs
@@ -18,7 +18,7 @@
#![allow(unused_must_use)]
#![allow(clippy::all)]
-use rustfs_common::data_usage::TierStats;
+use rustfs_data_usage::TierStats;
use sha2::Sha256;
use std::collections::HashMap;
use std::ops::Sub;
diff --git a/crates/ecstore/src/bucket/replication/replication_resyncer.rs b/crates/ecstore/src/bucket/replication/replication_resyncer.rs
index 093b3c372..37b9ba469 100644
--- a/crates/ecstore/src/bucket/replication/replication_resyncer.rs
+++ b/crates/ecstore/src/bucket/replication/replication_resyncer.rs
@@ -56,7 +56,7 @@ use rustfs_filemeta::{
ReplicationType, ReplicationWorkerOperation, ResyncDecision, ResyncTargetDecision, VersionPurgeStatusType,
get_replication_state, parse_replicate_decision, replication_statuses_map, target_reset_header, version_purge_statuses_map,
};
-use rustfs_s3_common::EventName;
+use rustfs_s3_types::EventName;
use rustfs_utils::http::{
AMZ_BUCKET_REPLICATION_STATUS, AMZ_OBJECT_TAGGING, AMZ_TAGGING_DIRECTIVE, CONTENT_ENCODING, HeaderExt as _,
SSEC_ALGORITHM_HEADER, SSEC_KEY_HEADER, SSEC_KEY_MD5_HEADER, SUFFIX_OBJECTLOCK_LEGALHOLD_TIMESTAMP,
diff --git a/crates/ecstore/src/data_usage.rs b/crates/ecstore/src/data_usage.rs
index defb7b20e..ed80acff8 100644
--- a/crates/ecstore/src/data_usage.rs
+++ b/crates/ecstore/src/data_usage.rs
@@ -27,7 +27,7 @@ pub use local_snapshot::{
data_usage_dir, data_usage_state_dir, ensure_data_usage_layout, read_snapshot as read_local_snapshot, snapshot_file_name,
snapshot_object_path, snapshot_path, write_snapshot as write_local_snapshot,
};
-use rustfs_common::data_usage::{
+use rustfs_data_usage::{
BucketTargetUsageInfo, BucketUsageInfo, DataUsageCache, DataUsageEntry, DataUsageInfo, DiskUsageStatus, SizeSummary,
};
use rustfs_io_metrics::record_system_path_failure;
@@ -686,7 +686,7 @@ pub async fn save_data_usage_cache(cache: &DataUsageCache, name: &str) -> crate:
#[cfg(test)]
mod tests {
use super::*;
- use rustfs_common::data_usage::BucketUsageInfo;
+ use rustfs_data_usage::BucketUsageInfo;
fn aggregate_for_test(
inputs: Vec<(DiskUsageStatus, Result