refactor(s3): consolidate semantic boundaries and remove s3-common (#3012)

* refactor(common): introduce rustfs-data-usage core crate

* refactor(concurrency): migrate workers crate into concurrency

* refactor(crypto): migrate appauth token APIs into crypto

* fix docs urls

* remove unused crate

* refactor(data-usage): switch consumers to rustfs-data-usage

* chore(fmt): apply cargo fmt and lockfile sync

* refactor(common): remove data_usage compatibility re-export

* refactor(capacity): move capacity_scope to object-capacity

* refactor(io-metrics): relocate internode metrics from common

* refactor(common): decouple scanner report from madmin

* chore(fmt): normalize import ordering after pre-commit

* refactor(s3): split s3 types and ops crates

* refactor(s3): centralize event version and safe parsing

* refactor(s3): add op-event compatibility guardrails

* refactor(s3): add runtime op-event mismatch observability

* refactor(s3): extract delete event mapping helper

* refactor(s3): extract put event mapping helper

* refactor(s3): consolidate remaining event semantic helpers

* refactor(s3): add op-event coverage checks and observability alerts

* refactor(s3-ops): consolidate op-event semantic mapping

* refactor(scanner): remove last_minute wrapper module

* refactor(scanner): consolidate duplicated data usage models
This commit is contained in:
houseme
2026-05-19 20:50:25 +08:00
committed by GitHub
parent d6fc70eb12
commit 73bde843d6
100 changed files with 982 additions and 2520 deletions
+3 -2
View File
@@ -61,7 +61,6 @@ workspace = true
[dependencies]
# RustFS Internal Crates
rustfs-heal = { workspace = true }
rustfs-appauth = { workspace = true }
rustfs-audit = { workspace = true }
rustfs-common = { workspace = true }
rustfs-config = { workspace = true, features = ["constants", "notify"] }
@@ -80,7 +79,9 @@ rustfs-policy = { workspace = true }
rustfs-protocols = { workspace = true }
rustfs-protos = { workspace = true }
rustfs-rio.workspace = true
rustfs-s3-common = { workspace = true }
rustfs-s3-types = { workspace = true }
rustfs-s3-ops = { workspace = true }
rustfs-data-usage = { workspace = true }
rustfs-s3select-api = { workspace = true }
rustfs-s3select-query = { workspace = true }
rustfs-targets = { workspace = true }
+1 -1
View File
@@ -27,8 +27,8 @@ use http::{HeaderMap, StatusCode};
use hyper::Method;
use matchit::Params;
use percent_encoding::percent_decode_str;
use rustfs_common::data_usage::TierStats;
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
use rustfs_data_usage::TierStats;
use rustfs_ecstore::bucket::lifecycle::bucket_lifecycle_ops::GLOBAL_TransitionState;
use rustfs_ecstore::{
bucket::lifecycle::tier_last_day_stats::DailyAllTierStats,
+2 -2
View File
@@ -65,7 +65,7 @@ use rustfs_filemeta::{ReplicationStatusType, ReplicationType};
use rustfs_madmin::utils::parse_duration;
use rustfs_notify::{Event as NotificationEvent, notification_system};
use rustfs_policy::policy::action::{Action, S3Action};
use rustfs_s3_common::EventName;
use rustfs_s3_types::EventName;
use rustfs_signer::pre_sign_v4;
use rustfs_utils::http::{
SUFFIX_SOURCE_DELETEMARKER, SUFFIX_SOURCE_MTIME, SUFFIX_SOURCE_REPLICATION_CHECK, SUFFIX_SOURCE_REPLICATION_REQUEST,
@@ -3803,7 +3803,7 @@ mod tests {
fn event_matches_listen_notification_respects_bucket_event_and_object_filters() {
let filter = ListenNotificationFilter {
bucket: Some("demo-bucket".to_string()),
event_mask: EventName::ObjectCreatedPut.mask() | EventName::ObjectCreatedPost.mask(),
event_mask: rustfs_s3_ops::put_object_created_event_mask(),
prefix: Some("logs/".to_string()),
suffix: Some(".json".to_string()),
};
+1 -1
View File
@@ -17,7 +17,7 @@
use crate::app::context::{AppContext, get_global_app_context};
use crate::capacity::resolve_admin_used_capacity;
use crate::error::ApiError;
use rustfs_common::data_usage::DataUsageInfo;
use rustfs_data_usage::DataUsageInfo;
use rustfs_ecstore::admin_server_info::get_server_info;
use rustfs_ecstore::data_usage::load_data_usage_from_backend;
use rustfs_ecstore::endpoints::EndpointServerPools;
+1 -1
View File
@@ -64,7 +64,7 @@ use rustfs_policy::policy::{
action::{Action, S3Action},
{BucketPolicy, BucketPolicyArgs, Effect, Validator},
};
use rustfs_s3_common::S3Operation;
use rustfs_s3_ops::S3Operation;
use rustfs_targets::{
EventName,
arn::{ARN, TargetIDError},
+1 -1
View File
@@ -52,7 +52,7 @@ use rustfs_filemeta::{ReplicationStatusType, ReplicationType};
use rustfs_rio::{CompressReader, EncryptReader, HashReader};
#[cfg(test)]
use rustfs_rio::{DecryptReader, HardLimitReader, boxed_reader, wrap_reader};
use rustfs_s3_common::S3Operation;
use rustfs_s3_ops::S3Operation;
use rustfs_targets::EventName;
use rustfs_utils::CompressionAlgorithm;
use rustfs_utils::http::{
+7 -15
View File
@@ -86,7 +86,7 @@ use rustfs_io_metrics;
use rustfs_notify::EventArgsBuilder;
use rustfs_policy::policy::action::{Action, S3Action};
use rustfs_rio::{CompressReader, DynReader, EncryptReader, HashReader, wrap_reader};
use rustfs_s3_common::S3Operation;
use rustfs_s3_ops::{S3Operation, delete_event_name_for_marker, put_event_name_for_post_object};
use rustfs_s3select_api::{
object_store::bytes_stream,
query::{Context, Query},
@@ -1625,9 +1625,9 @@ impl DefaultObjectUsecase {
fn put_object_execution_context(req: &S3Request<PutObjectInput>) -> (EventName, QuotaOperation, &'static str) {
if req.extensions.get::<PostObjectRequestMarker>().is_some() {
(EventName::ObjectCreatedPost, QuotaOperation::PostObject, "POST")
(put_event_name_for_post_object(true), QuotaOperation::PostObject, "POST")
} else {
(EventName::ObjectCreatedPut, QuotaOperation::PutObject, "PUT")
(put_event_name_for_post_object(false), QuotaOperation::PutObject, "PUT")
}
}
@@ -3141,11 +3141,7 @@ impl DefaultObjectUsecase {
let _activity_guard = DeleteTailActivityGuard::new(DeleteTailStage::Notify);
for res in delete_results {
if let Some(dobj) = res.delete_object {
let event_name = if dobj.delete_marker {
EventName::ObjectRemovedDeleteMarkerCreated
} else {
EventName::ObjectRemovedDelete
};
let event_name = delete_event_name_for_marker(dobj.delete_marker);
let event_args = EventArgsBuilder::new(
event_name,
bucket.clone(),
@@ -3310,7 +3306,7 @@ impl DefaultObjectUsecase {
}
// Prefix/force-delete returns empty ObjectInfo; still emit bucket notification so webhooks match S3 DELETE.
helper = helper
.event_name(EventName::ObjectRemovedDelete)
.event_name(delete_event_name_for_marker(false))
.object(ObjectInfo {
name: key.clone(),
bucket: bucket.clone(),
@@ -3378,11 +3374,7 @@ impl DefaultObjectUsecase {
..Default::default()
};
let event_name = if delete_marker {
EventName::ObjectRemovedDeleteMarkerCreated
} else {
EventName::ObjectRemovedDelete
};
let event_name = delete_event_name_for_marker(delete_marker);
helper = helper.event_name(event_name);
helper = helper
@@ -4341,7 +4333,7 @@ impl DefaultObjectUsecase {
};
let event_args = rustfs_notify::EventArgs {
event_name: EventName::ObjectCreatedPut,
event_name: put_event_name_for_post_object(false),
bucket_name: bucket.clone(),
object: obj_info.clone(),
req_params: req_params.clone(),
+2 -2
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use rustfs_appauth::token::{Token, parse_license_with_public_key};
use rustfs_crypto::{Token, parse_license_with_public_key};
use std::fmt;
use std::io::{Error, ErrorKind, Result};
use std::sync::Arc;
@@ -328,7 +328,7 @@ mod tests {
RsaPrivateKey, RsaPublicKey,
pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding},
};
use rustfs_appauth::token::sign_license_token;
use rustfs_crypto::sign_license_token;
use serial_test::serial;
#[test]
+20 -6
View File
@@ -16,7 +16,7 @@ use super::{module_switch::resolve_notify_module_state, refresh_persisted_module
use crate::app::context::resolve_server_config;
use rustfs_ecstore::event_notification::{EventArgs as EcstoreEventArgs, register_event_dispatch_hook};
use rustfs_notify::EventArgs as NotifyEventArgs;
use rustfs_s3_common::EventName;
use rustfs_s3_types::EventName;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicBool, Ordering};
use tokio::spawn;
@@ -38,14 +38,26 @@ pub fn is_notify_module_enabled() -> bool {
NOTIFY_MODULE_ENABLED.load(Ordering::Relaxed)
}
fn convert_ecstore_event_args(args: EcstoreEventArgs) -> NotifyEventArgs {
fn convert_ecstore_event_args(args: EcstoreEventArgs) -> Option<NotifyEventArgs> {
let version_id = args.object.version_id.map(|v| v.to_string()).unwrap_or_default();
let (host, port) = parse_host_and_port(args.host);
let req_params = args.req_params.into_iter().collect();
let resp_elements = args.resp_elements.into_iter().collect();
let event_name = match EventName::try_from_event_str(args.event_name.as_str()) {
Ok(event_name) => event_name,
Err(err) => {
warn!(
event_name = args.event_name,
bucket = args.bucket_name,
error = %err,
"dropping ecstore event with invalid event name"
);
return None;
}
};
NotifyEventArgs {
event_name: EventName::from(args.event_name.as_str()),
Some(NotifyEventArgs {
event_name,
bucket_name: args.bucket_name,
object: args.object,
req_params,
@@ -54,7 +66,7 @@ fn convert_ecstore_event_args(args: EcstoreEventArgs) -> NotifyEventArgs {
host,
port,
user_agent: args.user_agent,
}
})
}
fn parse_host_and_port(host: String) -> (String, u16) {
@@ -77,7 +89,9 @@ fn parse_host_and_port(host: String) -> (String, u16) {
fn install_ecstore_event_dispatch_hook() {
let installed = register_event_dispatch_hook(|args| {
let notify_args = convert_ecstore_event_args(args);
let Some(notify_args) = convert_ecstore_event_args(args) else {
return;
};
spawn(async move {
rustfs_notify::notifier_global::notify(notify_args).await;
});
+3 -2
View File
@@ -41,7 +41,8 @@ use rustfs_ecstore::{
new_object_layer_fn,
store_api::{BucketOperations, BucketOptions, ObjectLockRetentionOptions, ObjectOperations, ObjectOptions},
};
use rustfs_s3_common::{S3Operation, record_s3_op};
use rustfs_io_metrics::record_s3_op;
use rustfs_s3_ops::S3Operation;
use rustfs_targets::EventName;
use rustfs_utils::http::headers::{
AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER, AMZ_OBJECT_LOCK_MODE_LOWER, AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE_LOWER,
@@ -71,7 +72,7 @@ impl Default for FS {
impl FS {
pub fn new() -> Self {
rustfs_s3_common::init_s3_metrics();
rustfs_io_metrics::init_s3_metrics();
Self {}
}
+20 -3
View File
@@ -23,9 +23,10 @@ use rustfs_audit::{
global::AuditLogger,
};
use rustfs_ecstore::store_api::ObjectInfo;
use rustfs_io_metrics::record_s3_op;
use rustfs_notify::{EventArgsBuilder, notifier_global};
use rustfs_s3_common::record_s3_op;
use rustfs_s3_common::{EventName, S3Operation};
use rustfs_s3_ops::{S3Operation, operation_matches_event_name};
use rustfs_s3_types::EventName;
use rustfs_utils::{
extract_params_header, extract_req_params, extract_resp_elements, get_request_host, get_request_port, get_request_user_agent,
http::headers::AMZ_REQUEST_ID,
@@ -34,7 +35,7 @@ use s3s::{S3Request, S3Response, S3Result};
use serde_json::Value;
use std::future::Future;
use tokio::runtime::{Builder, Handle};
use tracing::{Instrument, info_span};
use tracing::{Instrument, info_span, warn};
/// Schedules an asynchronous task on the current runtime;
/// if there is no runtime, creates a minimal runtime execution on a new thread.
@@ -89,6 +90,22 @@ pub struct EnabledOperationHelper {
impl OperationHelper {
/// Create a new OperationHelper for S3 requests.
pub fn new(req: &S3Request<impl Send + Sync>, event: EventName, op: S3Operation) -> Self {
let op_event_matches = operation_matches_event_name(op, event);
debug_assert!(op_event_matches, "operation/event mismatch: op={} event={}", op.as_str(), event.as_str());
if !op_event_matches {
counter!(
"rustfs_log_chain_op_event_mismatch_total",
"op" => op.as_str(),
"event" => event.as_str().to_string()
)
.increment(1);
warn!(
op = op.as_str(),
event = event.as_str(),
"operation/event mismatch detected; check S3 semantic mapping"
);
}
let audit_enabled = is_audit_module_enabled();
let notify_enabled = should_build_notification_event(is_notify_module_enabled());
+1 -1
View File
@@ -13,7 +13,7 @@
// limitations under the License.
use super::*;
use rustfs_common::internode_metrics::{
use rustfs_io_metrics::internode_metrics::{
INTERNODE_OPERATION_GRPC_READ_ALL, INTERNODE_OPERATION_GRPC_WRITE_ALL, global_internode_metrics,
};
use serde::de::DeserializeOwned;
+4 -4
View File
@@ -18,15 +18,15 @@ use futures_util::TryStreamExt;
use http::{HeaderMap, Method, Request, Response, StatusCode, Uri};
use http_body_util::{BodyExt, Limited};
use hyper::body::Incoming;
use rustfs_common::internode_metrics::{
INTERNODE_OPERATION_PUT_FILE_STREAM, INTERNODE_OPERATION_READ_FILE_STREAM, INTERNODE_OPERATION_WALK_DIR,
global_internode_metrics,
};
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
use rustfs_ecstore::disk::{DiskAPI, WalkDirOptions};
use rustfs_ecstore::rpc::verify_rpc_signature;
use rustfs_ecstore::set_disk::DEFAULT_READ_BUFFER_SIZE;
use rustfs_ecstore::store::find_local_disk_by_ref;
use rustfs_io_metrics::internode_metrics::{
INTERNODE_OPERATION_PUT_FILE_STREAM, INTERNODE_OPERATION_READ_FILE_STREAM, INTERNODE_OPERATION_WALK_DIR,
global_internode_metrics,
};
use rustfs_utils::net::bytes_stream;
use s3s::Body;
use s3s::dto::StreamingBlob;