mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-06 21:33:14 +00:00
feat(storage): refactor audit and notification with OperationHelper (#825)
* improve code for audit * improve code ecfs.rs * improve code * improve code for ecfs.rs * feat(storage): refactor audit and notification with OperationHelper This commit introduces a significant refactoring of the audit logging and event notification mechanisms within `ecfs.rs`. The core of this change is the new `OperationHelper` struct, which encapsulates and simplifies the logic for both concerns. It replaces the previous `AuditHelper` and manual event dispatching. Key improvements include: - **Unified Handling**: `OperationHelper` manages both audit and notification builders, providing a single, consistent entry point for S3 operations. - **RAII for Automation**: By leveraging the `Drop` trait, the helper automatically dispatches logs and notifications when it goes out of scope. This simplifies S3 method implementations and ensures cleanup even on early returns. - **Fluent API**: A builder-like pattern with methods such as `.object()`, `.version_id()`, and `.suppress_event()` makes the code more readable and expressive. - **Context-Aware Logic**: The helper's `.complete()` method intelligently populates log details based on the operation's `S3Result` and only triggers notifications on success. - **Modular Design**: All helper logic is now isolated in `rustfs/src/storage/helper.rs`, improving separation of concerns and making `ecfs.rs` cleaner. This refactoring significantly enhances code clarity, reduces boilerplate, and improves the robustness of logging and notification handling across the storage layer. * fix * fix * fix * fix * fix * fix * fix * improve code for audit and notify * fix * fix * fix
This commit is contained in:
@@ -12,11 +12,22 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use rustfs_targets::TargetError;
|
||||
use rustfs_targets::arn::TargetID;
|
||||
use rustfs_targets::{TargetError, arn::TargetID};
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors related to the notification system's lifecycle.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LifecycleError {
|
||||
/// Error indicating the system has already been initialized.
|
||||
#[error("System has already been initialized")]
|
||||
AlreadyInitialized,
|
||||
|
||||
/// Error indicating the system has not been initialized yet.
|
||||
#[error("System has not been initialized")]
|
||||
NotInitialized,
|
||||
}
|
||||
|
||||
/// Error types for the notification system
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NotificationError {
|
||||
@@ -38,11 +49,8 @@ pub enum NotificationError {
|
||||
#[error("Rule configuration error: {0}")]
|
||||
RuleConfiguration(String),
|
||||
|
||||
#[error("System initialization error: {0}")]
|
||||
Initialization(String),
|
||||
|
||||
#[error("Notification system has already been initialized")]
|
||||
AlreadyInitialized,
|
||||
#[error("System lifecycle error: {0}")]
|
||||
Lifecycle(#[from] LifecycleError),
|
||||
|
||||
#[error("I/O error: {0}")]
|
||||
Io(io::Error),
|
||||
@@ -56,6 +64,9 @@ pub enum NotificationError {
|
||||
#[error("Target '{0}' not found")]
|
||||
TargetNotFound(TargetID),
|
||||
|
||||
#[error("Server not initialized")]
|
||||
ServerNotInitialized,
|
||||
#[error("System initialization error: {0}")]
|
||||
Initialization(String),
|
||||
|
||||
#[error("Storage not available: {0}")]
|
||||
StorageNotAvailable(String),
|
||||
}
|
||||
|
||||
@@ -276,3 +276,120 @@ impl EventArgs {
|
||||
self.req_params.contains_key("x-rustfs-source-replication-request")
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`EventArgs`].
|
||||
///
|
||||
/// This builder provides a fluent API to construct an `EventArgs` instance,
|
||||
/// ensuring that all required fields are provided.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// let args = EventArgsBuilder::new(
|
||||
/// EventName::ObjectCreatedPut,
|
||||
/// "my-bucket",
|
||||
/// object_info,
|
||||
/// )
|
||||
/// .host("localhost:9000")
|
||||
/// .user_agent("my-app/1.0")
|
||||
/// .build();
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EventArgsBuilder {
|
||||
event_name: EventName,
|
||||
bucket_name: String,
|
||||
object: rustfs_ecstore::store_api::ObjectInfo,
|
||||
req_params: HashMap<String, String>,
|
||||
resp_elements: HashMap<String, String>,
|
||||
version_id: String,
|
||||
host: String,
|
||||
user_agent: String,
|
||||
}
|
||||
|
||||
impl EventArgsBuilder {
|
||||
/// Creates a new builder with the required fields.
|
||||
pub fn new(event_name: EventName, bucket_name: impl Into<String>, object: rustfs_ecstore::store_api::ObjectInfo) -> Self {
|
||||
Self {
|
||||
event_name,
|
||||
bucket_name: bucket_name.into(),
|
||||
object,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the event name.
|
||||
pub fn event_name(mut self, event_name: EventName) -> Self {
|
||||
self.event_name = event_name;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the bucket name.
|
||||
pub fn bucket_name(mut self, bucket_name: impl Into<String>) -> Self {
|
||||
self.bucket_name = bucket_name.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the object information.
|
||||
pub fn object(mut self, object: rustfs_ecstore::store_api::ObjectInfo) -> Self {
|
||||
self.object = object;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the request parameters.
|
||||
pub fn req_params(mut self, req_params: HashMap<String, String>) -> Self {
|
||||
self.req_params = req_params;
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a single request parameter.
|
||||
pub fn req_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
||||
self.req_params.insert(key.into(), value.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the response elements.
|
||||
pub fn resp_elements(mut self, resp_elements: HashMap<String, String>) -> Self {
|
||||
self.resp_elements = resp_elements;
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a single response element.
|
||||
pub fn resp_element(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
||||
self.resp_elements.insert(key.into(), value.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the version ID.
|
||||
pub fn version_id(mut self, version_id: impl Into<String>) -> Self {
|
||||
self.version_id = version_id.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the host.
|
||||
pub fn host(mut self, host: impl Into<String>) -> Self {
|
||||
self.host = host.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the user agent.
|
||||
pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
|
||||
self.user_agent = user_agent.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the final `EventArgs` instance.
|
||||
///
|
||||
/// This method consumes the builder and returns the constructed `EventArgs`.
|
||||
pub fn build(self) -> EventArgs {
|
||||
EventArgs {
|
||||
event_name: self.event_name,
|
||||
bucket_name: self.bucket_name,
|
||||
object: self.object,
|
||||
req_params: self.req_params,
|
||||
resp_elements: self.resp_elements,
|
||||
version_id: self.version_id,
|
||||
host: self.host,
|
||||
user_agent: self.user_agent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-29
@@ -12,17 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{BucketNotificationConfig, Event, EventArgs, NotificationError, NotificationSystem};
|
||||
use once_cell::sync::Lazy;
|
||||
use crate::{BucketNotificationConfig, Event, EventArgs, LifecycleError, NotificationError, NotificationSystem};
|
||||
use rustfs_ecstore::config::Config;
|
||||
use rustfs_targets::EventName;
|
||||
use rustfs_targets::arn::TargetID;
|
||||
use rustfs_targets::{EventName, arn::TargetID};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use tracing::{error, instrument};
|
||||
use tracing::error;
|
||||
|
||||
static NOTIFICATION_SYSTEM: OnceLock<Arc<NotificationSystem>> = OnceLock::new();
|
||||
// Create a globally unique Notifier instance
|
||||
static GLOBAL_NOTIFIER: Lazy<Notifier> = Lazy::new(|| Notifier {});
|
||||
|
||||
/// Initialize the global notification system with the given configuration.
|
||||
/// This function should only be called once throughout the application life cycle.
|
||||
@@ -34,7 +30,7 @@ pub async fn initialize(config: Config) -> Result<(), NotificationError> {
|
||||
|
||||
match NOTIFICATION_SYSTEM.set(Arc::new(system)) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(NotificationError::AlreadyInitialized),
|
||||
Err(_) => Err(NotificationError::Lifecycle(LifecycleError::AlreadyInitialized)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,14 +45,11 @@ pub fn is_notification_system_initialized() -> bool {
|
||||
NOTIFICATION_SYSTEM.get().is_some()
|
||||
}
|
||||
|
||||
/// Returns a reference to the global Notifier instance.
|
||||
pub fn notifier_instance() -> &'static Notifier {
|
||||
&GLOBAL_NOTIFIER
|
||||
}
|
||||
/// A module providing the public API for event notification.
|
||||
pub mod notifier_global {
|
||||
use super::*;
|
||||
use tracing::instrument;
|
||||
|
||||
pub struct Notifier {}
|
||||
|
||||
impl Notifier {
|
||||
/// Notify an event asynchronously.
|
||||
/// This is the only entry point for all event notifications in the system.
|
||||
/// # Parameter
|
||||
@@ -67,8 +60,8 @@ impl Notifier {
|
||||
///
|
||||
/// # Using
|
||||
/// This function is used to notify events in the system, such as object creation, deletion, or updates.
|
||||
#[instrument(skip(self, args))]
|
||||
pub async fn notify(&self, args: EventArgs) {
|
||||
#[instrument(skip(args))]
|
||||
pub async fn notify(args: EventArgs) {
|
||||
// Dependency injection or service positioning mode obtain NotificationSystem instance
|
||||
let notification_sys = match notification_system() {
|
||||
// If the notification system itself cannot be retrieved, it will be returned directly
|
||||
@@ -110,7 +103,6 @@ impl Notifier {
|
||||
/// # Using
|
||||
/// This function allows you to dynamically add notification rules for a specific bucket.
|
||||
pub async fn add_bucket_notification_rule(
|
||||
&self,
|
||||
bucket_name: &str,
|
||||
region: &str,
|
||||
event_names: &[EventName],
|
||||
@@ -137,7 +129,7 @@ impl Notifier {
|
||||
// Get global NotificationSystem
|
||||
let notification_sys = match notification_system() {
|
||||
Some(sys) => sys,
|
||||
None => return Err(NotificationError::ServerNotInitialized),
|
||||
None => return Err(NotificationError::Lifecycle(LifecycleError::NotInitialized)),
|
||||
};
|
||||
|
||||
// Loading configuration
|
||||
@@ -159,7 +151,6 @@ impl Notifier {
|
||||
/// # Using
|
||||
/// Supports notification rules for adding multiple event types, prefixes, suffixes, and targets to the same bucket in batches.
|
||||
pub async fn add_event_specific_rules(
|
||||
&self,
|
||||
bucket_name: &str,
|
||||
region: &str,
|
||||
event_rules: &[(Vec<EventName>, String, String, Vec<TargetID>)],
|
||||
@@ -176,10 +167,7 @@ impl Notifier {
|
||||
}
|
||||
|
||||
// Get global NotificationSystem instance
|
||||
let notification_sys = match notification_system() {
|
||||
Some(sys) => sys,
|
||||
None => return Err(NotificationError::ServerNotInitialized),
|
||||
};
|
||||
let notification_sys = notification_system().ok_or(NotificationError::Lifecycle(LifecycleError::NotInitialized))?;
|
||||
|
||||
// Loading configuration
|
||||
notification_sys
|
||||
@@ -196,12 +184,9 @@ impl Notifier {
|
||||
/// This function allows you to clear all notification rules for a specific bucket.
|
||||
/// This is useful when you want to reset the notification configuration for a bucket.
|
||||
///
|
||||
pub async fn clear_bucket_notification_rules(&self, bucket_name: &str) -> Result<(), NotificationError> {
|
||||
pub async fn clear_bucket_notification_rules(bucket_name: &str) -> Result<(), NotificationError> {
|
||||
// Get global NotificationSystem instance
|
||||
let notification_sys = match notification_system() {
|
||||
Some(sys) => sys,
|
||||
None => return Err(NotificationError::ServerNotInitialized),
|
||||
};
|
||||
let notification_sys = notification_system().ok_or(NotificationError::Lifecycle(LifecycleError::NotInitialized))?;
|
||||
|
||||
// Clear configuration
|
||||
notification_sys.remove_bucket_notification_config(bucket_name).await;
|
||||
|
||||
@@ -199,7 +199,9 @@ impl NotificationSystem {
|
||||
F: FnMut(&mut Config) -> bool, // The closure returns a boolean value indicating whether the configuration has been changed
|
||||
{
|
||||
let Some(store) = rustfs_ecstore::global::new_object_layer_fn() else {
|
||||
return Err(NotificationError::ServerNotInitialized);
|
||||
return Err(NotificationError::StorageNotAvailable(
|
||||
"Failed to save target configuration: server storage not initialized".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let mut new_config = rustfs_ecstore::config::com::read_config_without_migrate(store.clone())
|
||||
|
||||
@@ -18,18 +18,18 @@
|
||||
//! It supports sending events to various targets
|
||||
//! (like Webhook and MQTT) and includes features like event persistence and retry on failure.
|
||||
|
||||
pub mod error;
|
||||
pub mod event;
|
||||
mod error;
|
||||
mod event;
|
||||
pub mod factory;
|
||||
pub mod global;
|
||||
mod global;
|
||||
pub mod integration;
|
||||
pub mod notifier;
|
||||
pub mod registry;
|
||||
pub mod rules;
|
||||
pub mod stream;
|
||||
// Re-exports
|
||||
pub use error::NotificationError;
|
||||
pub use event::{Event, EventArgs};
|
||||
pub use global::{initialize, is_notification_system_initialized, notification_system};
|
||||
|
||||
pub use error::{LifecycleError, NotificationError};
|
||||
pub use event::{Event, EventArgs, EventArgsBuilder};
|
||||
pub use global::{initialize, is_notification_system_initialized, notification_system, notifier_global};
|
||||
pub use integration::NotificationSystem;
|
||||
pub use rules::BucketNotificationConfig;
|
||||
|
||||
Reference in New Issue
Block a user