improve code for notify

This commit is contained in:
houseme
2025-06-23 03:34:05 +08:00
parent c7af6587f5
commit 928453db62
30 changed files with 521 additions and 989 deletions
-110
View File
@@ -1,110 +0,0 @@
use crate::{Event, EventName};
use std::collections::HashMap;
/// 事件参数
#[derive(Debug, Clone)]
pub struct EventArgs {
pub event_name: EventName,
pub bucket_name: String,
pub object_name: String,
pub object_size: Option<i64>,
pub object_etag: Option<String>,
pub object_version_id: Option<String>,
pub object_content_type: Option<String>,
pub object_user_metadata: Option<HashMap<String, String>>,
pub req_params: HashMap<String, String>,
pub resp_elements: HashMap<String, String>,
pub host: String,
pub user_agent: String,
}
impl EventArgs {
/// 转换为通知事件
pub fn to_event(&self) -> Event {
let event_time = chrono::Utc::now();
let unique_id = format!("{:X}", event_time.timestamp_nanos_opt().unwrap_or(0));
let mut resp_elements = HashMap::new();
if let Some(request_id) = self.resp_elements.get("requestId") {
resp_elements.insert("x-amz-request-id".to_string(), request_id.clone());
}
if let Some(node_id) = self.resp_elements.get("nodeId") {
resp_elements.insert("x-amz-id-2".to_string(), node_id.clone());
}
// RustFS 特定的自定义元素
// 注意:这里需要获取 endpoint 的逻辑在 Rust 中可能需要单独实现
resp_elements.insert("x-rustfs-origin-endpoint".to_string(), "".to_string());
// 添加 deployment ID
resp_elements.insert("x-rustfs-deployment-id".to_string(), "".to_string());
if let Some(content_length) = self.resp_elements.get("content-length") {
resp_elements.insert("content-length".to_string(), content_length.clone());
}
let key_name = &self.object_name;
// 注意:这里可能需要根据 escape 参数进行 URL 编码
let mut event = Event {
event_version: "2.0".to_string(),
event_source: "rustfs:s3".to_string(),
aws_region: self.req_params.get("region").cloned().unwrap_or_default(),
event_time,
event_name: self.event_name,
user_identity: crate::event::Identity {
principal_id: self
.req_params
.get("principalId")
.cloned()
.unwrap_or_default(),
},
request_parameters: self.req_params.clone(),
response_elements: resp_elements,
s3: crate::event::Metadata {
schema_version: "1.0".to_string(),
configuration_id: "Config".to_string(),
bucket: crate::event::Bucket {
name: self.bucket_name.clone(),
owner_identity: crate::event::Identity {
principal_id: self
.req_params
.get("principalId")
.cloned()
.unwrap_or_default(),
},
arn: format!("arn:aws:s3:::{}", self.bucket_name),
},
object: crate::event::Object {
key: key_name.clone(),
version_id: self.object_version_id.clone(),
sequencer: unique_id,
size: self.object_size,
etag: self.object_etag.clone(),
content_type: self.object_content_type.clone(),
user_metadata: Some(self.object_user_metadata.clone().unwrap_or_default()),
},
},
source: crate::event::Source {
host: self.host.clone(),
port: "".to_string(),
user_agent: self.user_agent.clone(),
},
};
// 检查是否为删除事件,如果是删除事件,某些字段应当为空
let is_removed_event = matches!(
self.event_name,
EventName::ObjectRemovedDelete | EventName::ObjectRemovedDeleteMarkerCreated
);
if is_removed_event {
event.s3.object.etag = None;
event.s3.object.size = None;
event.s3.object.content_type = None;
event.s3.object.user_metadata = None;
}
event
}
}
+6
View File
@@ -92,6 +92,12 @@ pub enum NotificationError {
#[error("System initialization error: {0}")]
Initialization(String),
#[error("Notification system has already been initialized")]
AlreadyInitialized,
#[error("Io error: {0}")]
Io(std::io::Error),
}
impl From<url::ParseError> for TargetError {
+102 -1
View File
@@ -2,6 +2,7 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use url::form_urlencoded;
/// Error returned when parsing event name string fails。
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -296,7 +297,7 @@ pub struct Bucket {
}
/// Represents the object that the event occurred on
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Object {
/// The key (name) of the object
pub key: String,
@@ -323,6 +324,7 @@ pub struct Object {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Metadata {
/// The schema version of the event
#[serde(rename = "s3SchemaVersion")]
pub schema_version: String,
/// The ID of the configuration that triggered the event
pub configuration_id: String,
@@ -340,11 +342,13 @@ pub struct Source {
/// The port on the host
pub port: String,
/// The user agent that caused the event
#[serde(rename = "userAgent")]
pub user_agent: String,
}
/// Represents a storage event
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Event {
/// The version of the event
pub event_version: String,
@@ -432,6 +436,85 @@ impl Event {
pub fn mask(&self) -> u64 {
self.event_name.mask()
}
pub fn new(args: EventArgs) -> Self {
let event_time = Utc::now().naive_local();
let unique_id = match args.object.mod_time {
Some(t) => format!("{:X}", t.unix_timestamp_nanos()),
None => format!("{:X}", event_time.and_utc().timestamp_nanos_opt().unwrap_or(0)),
};
let mut resp_elements = args.resp_elements.clone();
resp_elements
.entry("x-amz-request-id".to_string())
.or_insert_with(|| "".to_string());
resp_elements
.entry("x-amz-id-2".to_string())
.or_insert_with(|| "".to_string());
// ... Filling of other response elements
// URL encoding of object keys
let key_name = form_urlencoded::byte_serialize(args.object.name.as_bytes()).collect::<String>();
let principal_id = args.req_params.get("principalId").cloned().unwrap_or_default();
let owner_identity = Identity {
principal_id: principal_id.clone(),
};
let user_identity = Identity { principal_id };
let mut s3_metadata = Metadata {
schema_version: "1.0".to_string(),
configuration_id: "Config".to_string(), // or from args
bucket: Bucket {
name: args.bucket_name.clone(),
owner_identity,
arn: format!("arn:aws:s3:::{}", args.bucket_name),
},
object: Object {
key: key_name,
version_id: Some(args.object.version_id.unwrap().to_string()),
sequencer: unique_id,
..Default::default()
},
};
let is_removed_event = matches!(
args.event_name,
EventName::ObjectRemovedDelete | EventName::ObjectRemovedDeleteMarkerCreated
);
if !is_removed_event {
s3_metadata.object.size = Some(args.object.size);
s3_metadata.object.etag = args.object.etag.clone();
s3_metadata.object.content_type = args.object.content_type.clone();
// Filter out internal reserved metadata
let user_metadata = args
.object
.user_defined
.iter()
.filter(|&(k, v)| !k.to_lowercase().starts_with("x-amz-meta-internal-"))
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<HashMap<String, String>>();
s3_metadata.object.user_metadata = Some(user_metadata);
}
Self {
event_version: "2.1".to_string(),
event_source: "rustfs:s3".to_string(),
aws_region: args.req_params.get("region").cloned().unwrap_or_default(),
event_time: event_time.and_utc(),
event_name: args.event_name,
user_identity,
request_parameters: args.req_params,
response_elements: resp_elements,
s3: s3_metadata,
source: Source {
host: args.host,
port: "".to_string(),
user_agent: args.user_agent,
},
}
}
}
/// Represents a log of events for sending to targets
@@ -444,3 +527,21 @@ pub struct EventLog {
/// The list of events
pub records: Vec<Event>,
}
#[derive(Debug, Clone)]
pub struct EventArgs {
pub event_name: EventName,
pub bucket_name: String,
pub object: ecstore::store_api::ObjectInfo,
pub req_params: HashMap<String, String>,
pub resp_elements: HashMap<String, String>,
pub host: String,
pub user_agent: String,
}
impl EventArgs {
// Helper function to check if it is a copy request
pub fn is_replication_request(&self) -> bool {
self.req_params.contains_key("x-rustfs-source-replication-request")
}
}
+12 -10
View File
@@ -4,7 +4,7 @@ use crate::{
target::{mqtt::MQTTArgs, webhook::WebhookArgs, Target},
};
use async_trait::async_trait;
use ecstore::config::KVS;
use ecstore::config::{ENABLE_KEY, ENABLE_ON, KVS};
use rumqttc::QoS;
use std::time::Duration;
use tracing::warn;
@@ -13,7 +13,6 @@ use url::Url;
// --- Configuration Constants ---
// General
pub const ENABLE: &str = "enable";
pub const DEFAULT_TARGET: &str = "1";
@@ -37,6 +36,9 @@ pub const NOTIFY_POSTGRES_SUB_SYS: &str = "notify_postgres";
pub const NOTIFY_REDIS_SUB_SYS: &str = "notify_redis";
pub const NOTIFY_WEBHOOK_SUB_SYS: &str = "notify_webhook";
#[allow(dead_code)]
pub const NOTIFY_SUB_SYSTEMS: &[&str] = &[NOTIFY_MQTT_SUB_SYS, NOTIFY_WEBHOOK_SUB_SYS];
// Webhook Keys
pub const WEBHOOK_ENDPOINT: &str = "endpoint";
pub const WEBHOOK_AUTH_TOKEN: &str = "auth_token";
@@ -111,8 +113,8 @@ impl TargetFactory for WebhookTargetFactory {
async fn create_target(&self, id: String, config: &KVS) -> Result<Box<dyn Target + Send + Sync>, TargetError> {
let get = |base_env_key: &str, config_key: &str| get_config_value(&id, base_env_key, config_key, config);
let enable = get(ENV_WEBHOOK_ENABLE, ENABLE)
.map(|v| v.eq_ignore_ascii_case("on") || v.eq_ignore_ascii_case("true"))
let enable = get(ENV_WEBHOOK_ENABLE, ENABLE_KEY)
.map(|v| v.eq_ignore_ascii_case(ENABLE_ON) || v.eq_ignore_ascii_case("true"))
.unwrap_or(false);
if !enable {
@@ -151,8 +153,8 @@ impl TargetFactory for WebhookTargetFactory {
fn validate_config(&self, id: &str, config: &KVS) -> Result<(), TargetError> {
let get = |base_env_key: &str, config_key: &str| get_config_value(id, base_env_key, config_key, config);
let enable = get(ENV_WEBHOOK_ENABLE, ENABLE)
.map(|v| v.eq_ignore_ascii_case("on") || v.eq_ignore_ascii_case("true"))
let enable = get(ENV_WEBHOOK_ENABLE, ENABLE_KEY)
.map(|v| v.eq_ignore_ascii_case(ENABLE_ON) || v.eq_ignore_ascii_case("true"))
.unwrap_or(false);
if !enable {
@@ -189,8 +191,8 @@ impl TargetFactory for MQTTTargetFactory {
async fn create_target(&self, id: String, config: &KVS) -> Result<Box<dyn Target + Send + Sync>, TargetError> {
let get = |base_env_key: &str, config_key: &str| get_config_value(&id, base_env_key, config_key, config);
let enable = get(ENV_MQTT_ENABLE, ENABLE)
.map(|v| v.eq_ignore_ascii_case("on") || v.eq_ignore_ascii_case("true"))
let enable = get(ENV_MQTT_ENABLE, ENABLE_KEY)
.map(|v| v.eq_ignore_ascii_case(ENABLE_ON) || v.eq_ignore_ascii_case("true"))
.unwrap_or(false);
if !enable {
@@ -252,8 +254,8 @@ impl TargetFactory for MQTTTargetFactory {
fn validate_config(&self, id: &str, config: &KVS) -> Result<(), TargetError> {
let get = |base_env_key: &str, config_key: &str| get_config_value(id, base_env_key, config_key, config);
let enable = get(ENV_MQTT_ENABLE, ENABLE)
.map(|v| v.eq_ignore_ascii_case("on") || v.eq_ignore_ascii_case("true"))
let enable = get(ENV_MQTT_ENABLE, ENABLE_KEY)
.map(|v| v.eq_ignore_ascii_case(ENABLE_ON) || v.eq_ignore_ascii_case("true"))
.unwrap_or(false);
if !enable {
+56 -8
View File
@@ -1,12 +1,60 @@
use crate::NotificationSystem;
use crate::{Event, EventArgs, NotificationError, NotificationSystem};
use ecstore::config::Config;
use once_cell::sync::Lazy;
use std::sync::Arc;
use std::sync::{Arc, OnceLock};
static NOTIFICATION_SYSTEM: Lazy<Arc<NotificationSystem>> =
Lazy::new(|| Arc::new(NotificationSystem::new()));
static NOTIFICATION_SYSTEM: OnceLock<Arc<NotificationSystem>> = OnceLock::new();
// Create a globally unique Notifier instance
pub static GLOBAL_NOTIFIER: Lazy<Notifier> = Lazy::new(|| Notifier {});
/// Returns the handle to the global NotificationSystem instance.
/// This function can be called anywhere you need to interact with the notification system。
pub fn notification_system() -> Arc<NotificationSystem> {
NOTIFICATION_SYSTEM.clone()
/// Initialize the global notification system with the given configuration.
/// This function should only be called once throughout the application life cycle.
pub async fn initialize(config: Config) -> Result<(), NotificationError> {
// `new` is synchronous and responsible for creating instances
let system = NotificationSystem::new(config);
// `init` is asynchronous and responsible for performing I/O-intensive initialization
system.init().await?;
match NOTIFICATION_SYSTEM.set(Arc::new(system)) {
Ok(_) => Ok(()),
Err(_) => Err(NotificationError::AlreadyInitialized),
}
}
/// Returns a handle to the global NotificationSystem instance.
/// Return None if the system has not been initialized.
pub fn notification_system() -> Option<Arc<NotificationSystem>> {
NOTIFICATION_SYSTEM.get().cloned()
}
pub struct Notifier {
// Notifier can hold state, but in this design we make it stateless,
// Rely on getting an instance of NotificationSystem from the outside.
}
impl crate::notifier::Notifier {
/// Notify an event asynchronously.
/// This is the only entry point for all event notifications in the system.
pub async fn notify(&self, 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
Some(sys) => sys,
None => {
tracing::error!("Notification system is not initialized.");
return;
}
};
// Avoid generating notifications for replica creation events
if args.is_replication_request() {
return;
}
// Create an event and send it
let event = Event::new(args.clone());
notification_sys
.send_event(&args.bucket_name, &args.event_name.as_str(), &args.object.name.clone(), event)
.await;
}
}
+89 -55
View File
@@ -40,7 +40,7 @@ impl NotificationMetrics {
}
}
// 提供公共方法增加计数
// Provide public methods to increase count
pub fn increment_processing(&self) {
self.processing_events.fetch_add(1, Ordering::Relaxed);
}
@@ -55,7 +55,7 @@ impl NotificationMetrics {
self.failed_events.fetch_add(1, Ordering::Relaxed);
}
// 提供公共方法获取计数
// Provide public methods to get count
pub fn processing_count(&self) -> usize {
self.processing_events.load(Ordering::Relaxed)
}
@@ -89,19 +89,13 @@ pub struct NotificationSystem {
metrics: Arc<NotificationMetrics>,
}
impl Default for NotificationSystem {
fn default() -> Self {
Self::new()
}
}
impl NotificationSystem {
/// Creates a new NotificationSystem
pub fn new() -> Self {
pub fn new(config: Config) -> Self {
NotificationSystem {
notifier: Arc::new(EventNotifier::new()),
registry: Arc::new(TargetRegistry::new()),
config: Arc::new(RwLock::new(Config::new())),
config: Arc::new(RwLock::new(config)),
stream_cancellers: Arc::new(RwLock::new(HashMap::new())),
concurrency_limiter: Arc::new(Semaphore::new(
std::env::var("RUSTFS_TARGET_STREAM_CONCURRENCY")
@@ -194,49 +188,43 @@ impl NotificationSystem {
pub async fn remove_target(&self, target_id: &TargetID, target_type: &str) -> Result<(), NotificationError> {
info!("Attempting to remove target: {}", target_id);
// Step 1: Stop the event stream (if present)
let mut cancellers_guard = self.stream_cancellers.write().await;
if let Some(cancel_tx) = cancellers_guard.remove(target_id) {
info!("Stopping event stream for target {}", target_id);
// Send a stop signal and continue execution even if it fails, because the receiver may have been closed
if let Err(e) = cancel_tx.send(()).await {
error!("Failed to send stop signal to target {} stream: {}", target_id, e);
}
} else {
info!("No active event stream found for target {}, skipping stop.", target_id);
}
drop(cancellers_guard);
let Some(store) = ecstore::global::new_object_layer_fn() else {
return Err(NotificationError::Io(std::io::Error::new(
std::io::ErrorKind::Other,
"errServerNotInitialized",
)));
};
// Step 2: Remove the Target instance from the activity list of Notifier
// TargetList::remove_target_only will call target.close()
let target_list = self.notifier.target_list();
let mut target_list_guard = target_list.write().await;
if target_list_guard.remove_target_only(target_id).await.is_some() {
info!("Removed target {} from the active list.", target_id);
} else {
warn!("Target {} was not found in the active list.", target_id);
}
drop(target_list_guard);
let mut new_config = ecstore::config::com::read_config_without_migrate(store.clone())
.await
.map_err(|e| NotificationError::Configuration(format!("Failed to read notification config: {}", e)))?;
// Step 3: Remove Target from persistent configuration
let mut config_guard = self.config.write().await;
let mut changed = false;
if let Some(targets_of_type) = config_guard.0.get_mut(target_type) {
if let Some(targets_of_type) = new_config.0.get_mut(target_type) {
if targets_of_type.remove(&target_id.name).is_some() {
info!("Removed target {} from the configuration.", target_id);
changed = true;
}
// If there are no targets under this type, remove the entry for this type
if targets_of_type.is_empty() {
config_guard.0.remove(target_type);
new_config.0.remove(target_type);
}
}
if !changed {
warn!("Target {} was not found in the configuration.", target_id);
return Ok(());
}
Ok(())
if let Err(e) = ecstore::config::com::save_server_config(store, &new_config).await {
error!("Failed to save config for target removal: {}", e);
return Err(NotificationError::Configuration(format!("Failed to save config: {}", e)));
}
info!(
"Configuration updated and persisted for target {} removal. Reloading system...",
target_id
);
self.reload_config(new_config).await
}
/// Set or update a Target configuration.
@@ -253,18 +241,49 @@ impl NotificationSystem {
/// If the target configuration is invalid, it returns Err(NotificationError::Configuration).
pub async fn set_target_config(&self, target_type: &str, target_name: &str, kvs: KVS) -> Result<(), NotificationError> {
info!("Setting config for target {} of type {}", target_name, target_type);
let mut config_guard = self.config.write().await;
config_guard
// 1. Get the storage handle
let Some(store) = ecstore::global::new_object_layer_fn() else {
return Err(NotificationError::Io(std::io::Error::new(
std::io::ErrorKind::Other,
"errServerNotInitialized",
)));
};
// 2. Read the latest configuration from storage
let mut new_config = ecstore::config::com::read_config_without_migrate(store.clone())
.await
.map_err(|e| NotificationError::Configuration(format!("Failed to read notification config: {}", e)))?;
// 3. Modify the configuration copy
new_config
.0
.entry(target_type.to_string())
.or_default()
.insert(target_name.to_string(), kvs);
let new_config = config_guard.clone();
// Release the lock before calling reload_config
drop(config_guard);
// 4. Persist the new configuration
if let Err(e) = ecstore::config::com::save_server_config(store, &new_config).await {
error!("Failed to save notification config: {}", e);
return Err(NotificationError::Configuration(format!("Failed to save notification config: {}", e)));
}
self.reload_config(new_config).await
// 5. After the persistence is successful, the system will be reloaded to apply changes.
match self.reload_config(new_config).await {
Ok(_) => {
info!(
"Target {} of type {} configuration updated and reloaded successfully",
target_name, target_type
);
Ok(())
}
Err(e) => {
error!("Failed to reload config for target {} of type {}: {}", target_name, target_type, e);
Err(NotificationError::Configuration(format!(
"Configuration saved, but failed to reload: {}",
e
)))
}
}
}
/// Removes all notification configurations for a bucket.
@@ -286,27 +305,42 @@ impl NotificationSystem {
/// If the target configuration does not exist, it returns Ok(()) without making any changes.
pub async fn remove_target_config(&self, target_type: &str, target_name: &str) -> Result<(), NotificationError> {
info!("Removing config for target {} of type {}", target_name, target_type);
let mut config_guard = self.config.write().await;
let mut changed = false;
let Some(store) = ecstore::global::new_object_layer_fn() else {
return Err(NotificationError::Io(std::io::Error::new(
std::io::ErrorKind::Other,
"errServerNotInitialized",
)));
};
if let Some(targets) = config_guard.0.get_mut(target_type) {
let mut new_config = ecstore::config::com::read_config_without_migrate(store.clone())
.await
.map_err(|e| NotificationError::Configuration(format!("Failed to read notification config: {}", e)))?;
let mut changed = false;
if let Some(targets) = new_config.0.get_mut(target_type) {
if targets.remove(target_name).is_some() {
changed = true;
}
if targets.is_empty() {
config_guard.0.remove(target_type);
new_config.0.remove(target_type);
}
}
if changed {
let new_config = config_guard.clone();
// Release the lock before calling reload_config
drop(config_guard);
self.reload_config(new_config).await
} else {
if !changed {
info!("Target {} of type {} not found, no changes made.", target_name, target_type);
Ok(())
return Ok(());
}
if let Err(e) = ecstore::config::com::save_server_config(store, &new_config).await {
error!("Failed to save config for target removal: {}", e);
return Err(NotificationError::Configuration(format!("Failed to save config: {}", e)));
}
info!(
"Configuration updated and persisted for target {} removal. Reloading system...",
target_name
);
self.reload_config(new_config).await
}
/// Enhanced event stream startup function, including monitoring and concurrency control
+2 -3
View File
@@ -4,7 +4,6 @@
//! similar to RustFS's notification system. It supports sending events to various targets
//! (like Webhook and MQTT) and includes features like event persistence and retry on failure.
pub mod args;
pub mod arn;
pub mod error;
pub mod event;
@@ -17,11 +16,11 @@ pub mod rules;
pub mod store;
pub mod stream;
pub mod target;
pub mod utils;
// Re-exports
pub use error::{NotificationError, StoreError, TargetError};
pub use event::{Event, EventLog, EventName};
pub use event::{Event, EventArgs, EventLog, EventName};
pub use global::{initialize, notification_system};
pub use integration::NotificationSystem;
pub use rules::BucketNotificationConfig;
use std::io::IsTerminal;
+22 -45
View File
@@ -96,57 +96,42 @@ impl EventNotifier {
let target_ids_len = target_ids.len();
let mut handles = vec![];
// 使用作用域来限制 target_list 的借用范围
// Use scope to limit the borrow scope of target_list
{
let target_list_guard = self.target_list.read().await;
info!("Sending event to targets: {:?}", target_ids);
for target_id in target_ids {
// `get` now returns Option<Arc<dyn Target + Send + Sync>>
if let Some(target_arc) = target_list_guard.get(&target_id) {
// 克隆 Arc<Box<dyn Target>> (target_list 存储的就是这个类型) 以便移入异步任务
// Clone an Arc<Box<dyn Target>> (which is where target_list is stored) to move into an asynchronous task
// target_arc is already Arc, clone it for the async task
let cloned_target_for_task = target_arc.clone();
let event_clone = event.clone();
let target_name_for_task = cloned_target_for_task.name(); // 在生成任务前获取名称
debug!(
"Preparing to send event to target: {}",
target_name_for_task
);
// 在闭包中使用克隆的数据,避免借用冲突
let target_name_for_task = cloned_target_for_task.name(); // Get the name before generating the task
debug!("Preparing to send event to target: {}", target_name_for_task);
// Use cloned data in closures to avoid borrowing conflicts
let handle = tokio::spawn(async move {
if let Err(e) = cloned_target_for_task.save(event_clone).await {
error!(
"Failed to send event to target {}: {}",
target_name_for_task, e
);
error!("Failed to send event to target {}: {}", target_name_for_task, e);
} else {
debug!(
"Successfully saved event to target {}",
target_name_for_task
);
debug!("Successfully saved event to target {}", target_name_for_task);
}
});
handles.push(handle);
} else {
warn!(
"Target ID {:?} found in rules but not in target list.",
target_id
);
warn!("Target ID {:?} found in rules but not in target list.", target_id);
}
}
// target_list 在这里自动释放
// target_list is automatically released here
}
// 等待所有任务完成
// Wait for all tasks to be completed
for handle in handles {
if let Err(e) = handle.await {
error!("Task for sending/saving event failed: {}", e);
}
}
info!(
"Event processing initiated for {} targets for bucket: {}",
target_ids_len, bucket_name
);
info!("Event processing initiated for {} targets for bucket: {}", target_ids_len, bucket_name);
} else {
debug!("No rules found for bucket: {}", bucket_name);
}
@@ -158,22 +143,22 @@ impl EventNotifier {
&self,
targets_to_init: Vec<Box<dyn Target + Send + Sync>>,
) -> Result<(), NotificationError> {
// 当前激活的、更简单的逻辑:
let mut target_list_guard = self.target_list.write().await; // 获取 TargetList 的写锁
// Currently active, simpler logic
let mut target_list_guard = self.target_list.write().await; //Gets a write lock for the TargetList
for target_boxed in targets_to_init {
// 遍历传入的 Box<dyn Target>
// Traverse the incoming Box<dyn Target >
debug!("init bucket target: {}", target_boxed.name());
// TargetList::add 方法期望 Arc<dyn Target + Send + Sync>
// 因此,需要将 Box<dyn Target + Send + Sync> 转换为 Arc<dyn Target + Send + Sync>
// TargetList::add method expectations Arc<dyn Target + Send + Sync>
// Therefore, you need to convert Box<dyn Target + Send + Sync> to Arc<dyn Target + Send + Sync>
let target_arc: Arc<dyn Target + Send + Sync> = Arc::from(target_boxed);
target_list_guard.add(target_arc)?; // Arc<dyn Target> 添加到列表中
target_list_guard.add(target_arc)?; // Add Arc<dyn Target> to the list
}
info!(
"Initialized {} targets, list size: {}", // 更清晰的日志
"Initialized {} targets, list size: {}", // Clearer logs
target_list_guard.len(),
target_list_guard.len()
);
Ok(()) // 确保返回 Result
Ok(()) // Make sure to return a Result
}
}
@@ -191,9 +176,7 @@ impl Default for TargetList {
impl TargetList {
/// Creates a new TargetList
pub fn new() -> Self {
TargetList {
targets: HashMap::new(),
}
TargetList { targets: HashMap::new() }
}
/// Adds a target to the list
@@ -201,10 +184,7 @@ impl TargetList {
let id = target.id();
if self.targets.contains_key(&id) {
// Potentially update or log a warning/error if replacing an existing target.
warn!(
"Target with ID {} already exists in TargetList. It will be overwritten.",
id
);
warn!("Target with ID {} already exists in TargetList. It will be overwritten.", id);
}
self.targets.insert(id, target);
Ok(())
@@ -212,10 +192,7 @@ impl TargetList {
/// Removes a target by ID. Note: This does not stop its associated event stream.
/// Stream cancellation should be handled by EventNotifier.
pub async fn remove_target_only(
&mut self,
id: &TargetID,
) -> Option<Arc<dyn Target + Send + Sync>> {
pub async fn remove_target_only(&mut self, id: &TargetID) -> Option<Arc<dyn Target + Send + Sync>> {
if let Some(target_arc) = self.targets.remove(id) {
if let Err(e) = target_arc.close().await {
// Target's own close logic
+2 -8
View File
@@ -4,7 +4,7 @@ use crate::{
factory::{MQTTTargetFactory, TargetFactory, WebhookTargetFactory},
target::Target,
};
use ecstore::config::{Config, KVS};
use ecstore::config::{Config, ENABLE_KEY, ENABLE_OFF, ENABLE_ON, KVS};
use std::collections::HashMap;
use tracing::{error, info};
@@ -74,7 +74,7 @@ impl TargetRegistry {
// Iterate through subsections (each representing a target instance)
for (target_id, target_config) in subsections {
// Skip disabled targets
if target_config.lookup("enable").unwrap_or_else(|| "off".to_string()) != "on" {
if target_config.lookup(ENABLE_KEY).unwrap_or_else(|| ENABLE_OFF.to_string()) != ENABLE_ON {
continue;
}
@@ -94,9 +94,3 @@ impl TargetRegistry {
Ok(targets)
}
}
#[cfg(test)]
mod tests {
#[tokio::test]
async fn test_target_registry() {}
}
+48 -100
View File
@@ -4,7 +4,6 @@ use crate::{
arn::TargetID, error::TargetError,
event::{Event, EventLog},
store::{Key, Store},
utils,
StoreError,
Target,
};
@@ -56,18 +55,14 @@ impl WebhookArgs {
if !self.queue_dir.is_empty() {
let path = std::path::Path::new(&self.queue_dir);
if !path.is_absolute() {
return Err(TargetError::Configuration(
"webhook queueDir path should be absolute".to_string(),
));
return Err(TargetError::Configuration("webhook queueDir path should be absolute".to_string()));
}
}
if !self.client_cert.is_empty() && self.client_key.is_empty()
|| self.client_cert.is_empty() && !self.client_key.is_empty()
{
return Err(TargetError::Configuration(
"cert and key must be specified as a pair".to_string(),
));
return Err(TargetError::Configuration("cert and key must be specified as a pair".to_string()));
}
Ok(())
@@ -79,7 +74,7 @@ pub struct WebhookTarget {
id: TargetID,
args: WebhookArgs,
http_client: Arc<Client>,
// 添加 Send + Sync 约束确保线程安全
// Add Send + Sync constraints to ensure thread safety
store: Option<Box<dyn Store<Event, Error = StoreError, Key = Key> + Send + Sync>>,
initialized: AtomicBool,
addr: String,
@@ -103,36 +98,35 @@ impl WebhookTarget {
/// Creates a new WebhookTarget
#[instrument(skip(args), fields(target_id = %id))]
pub fn new(id: String, args: WebhookArgs) -> Result<Self, TargetError> {
// 首先验证参数
// First verify the parameters
args.validate()?;
// 创建 TargetID
// Create a TargetID
let target_id = TargetID::new(id, ChannelTargetType::Webhook.as_str().to_string());
// 构建 HTTP client
// Build HTTP client
let mut client_builder = Client::builder()
.timeout(Duration::from_secs(30))
.user_agent(utils::get_user_agent(utils::ServiceType::Basis));
.user_agent(rustfs_utils::sys::get_user_agent(rustfs_utils::sys::ServiceType::Basis));
// 补充证书处理逻辑
// Supplementary certificate processing logic
if !args.client_cert.is_empty() && !args.client_key.is_empty() {
// 添加客户端证书
let cert = std::fs::read(&args.client_cert).map_err(|e| {
TargetError::Configuration(format!("Failed to read client cert: {}", e))
})?;
let key = std::fs::read(&args.client_key).map_err(|e| {
TargetError::Configuration(format!("Failed to read client key: {}", e))
})?;
// Add client certificate
let cert = std::fs::read(&args.client_cert)
.map_err(|e| TargetError::Configuration(format!("Failed to read client cert: {}", e)))?;
let key = std::fs::read(&args.client_key)
.map_err(|e| TargetError::Configuration(format!("Failed to read client key: {}", e)))?;
let identity = reqwest::Identity::from_pem(&[cert, key].concat()).map_err(|e| {
TargetError::Configuration(format!("Failed to create identity: {}", e))
})?;
let identity = reqwest::Identity::from_pem(&[cert, key].concat())
.map_err(|e| TargetError::Configuration(format!("Failed to create identity: {}", e)))?;
client_builder = client_builder.identity(identity);
}
let http_client = Arc::new(client_builder.build().map_err(|e| {
TargetError::Configuration(format!("Failed to build HTTP client: {}", e))
})?);
let http_client = Arc::new(
client_builder
.build()
.map_err(|e| TargetError::Configuration(format!("Failed to build HTTP client: {}", e)))?,
);
// 构建存储
// Build storage
let queue_store = if !args.queue_dir.is_empty() {
let queue_dir = PathBuf::from(&args.queue_dir).join(format!(
"rustfs-{}-{}-{}",
@@ -140,43 +134,30 @@ impl WebhookTarget {
target_id.name,
target_id.id
));
let store = super::super::store::QueueStore::<Event>::new(
queue_dir,
args.queue_limit,
STORE_EXTENSION,
);
let store = super::super::store::QueueStore::<Event>::new(queue_dir, args.queue_limit, STORE_EXTENSION);
if let Err(e) = store.open() {
error!(
"Failed to open store for Webhook target {}: {}",
target_id.id, e
);
error!("Failed to open store for Webhook target {}: {}", target_id.id, e);
return Err(TargetError::Storage(format!("{}", e)));
}
// 确保 QueueStore 实现的 Store trait 匹配预期的错误类型
Some(Box::new(store)
as Box<
dyn Store<Event, Error = StoreError, Key = Key> + Send + Sync,
>)
// Make sure that the Store trait implemented by QueueStore matches the expected error type
Some(Box::new(store) as Box<dyn Store<Event, Error = StoreError, Key = Key> + Send + Sync>)
} else {
None
};
// 解析地址
// resolved address
let addr = {
let host = args.endpoint.host_str().unwrap_or("localhost");
let port = args.endpoint.port().unwrap_or_else(|| {
if args.endpoint.scheme() == "https" {
443
} else {
80
}
});
let port = args
.endpoint
.port()
.unwrap_or_else(|| if args.endpoint.scheme() == "https" { 443 } else { 80 });
format!("{}:{}", host, port)
};
// 创建取消通道
// Create a cancel channel
let (cancel_sender, _) = mpsc::channel(1);
info!(target_id = %target_id.id, "Webhook target created");
Ok(WebhookTarget {
@@ -202,10 +183,7 @@ impl WebhookTarget {
return Err(TargetError::NotConnected);
}
Err(e) => {
error!(
"Failed to check if Webhook target {} is active: {}",
self.id, e
);
error!("Failed to check if Webhook target {} is active: {}", self.id, e);
return Err(e);
}
}
@@ -228,17 +206,13 @@ impl WebhookTarget {
records: vec![event.clone()],
};
let data = serde_json::to_vec(&log)
.map_err(|e| TargetError::Serialization(format!("Failed to serialize event: {}", e)))?;
let data =
serde_json::to_vec(&log).map_err(|e| TargetError::Serialization(format!("Failed to serialize event: {}", e)))?;
// Vec<u8> 转换为 String
let data_string = String::from_utf8(data.clone()).map_err(|e| {
TargetError::Encoding(format!("Failed to convert event data to UTF-8: {}", e))
})?;
debug!(
"Sending event to webhook target: {}, event log: {}",
self.id, data_string
);
let data_string = String::from_utf8(data.clone())
.map_err(|e| TargetError::Encoding(format!("Failed to convert event data to UTF-8: {}", e)))?;
debug!("Sending event to webhook target: {}, event log: {}", self.id, data_string);
// 构建请求
let mut req_builder = self
@@ -256,8 +230,7 @@ impl WebhookTarget {
}
1 => {
// 只有令牌,需要添加 "Bearer" 前缀
req_builder = req_builder
.header("Authorization", format!("Bearer {}", self.args.auth_token));
req_builder = req_builder.header("Authorization", format!("Bearer {}", self.args.auth_token));
}
_ => {
// 空字符串或其他情况,不添加认证头
@@ -305,16 +278,8 @@ impl Target for WebhookTarget {
.map_err(|e| TargetError::Network(format!("Failed to resolve host: {}", e)))?
.next()
.ok_or_else(|| TargetError::Network("No address found".to_string()))?;
debug!(
"is_active socket addr: {},target id:{}",
socket_addr, self.id.id
);
match tokio::time::timeout(
Duration::from_secs(5),
tokio::net::TcpStream::connect(socket_addr),
)
.await
{
debug!("is_active socket addr: {},target id:{}", socket_addr, self.id.id);
match tokio::time::timeout(Duration::from_secs(5), tokio::net::TcpStream::connect(socket_addr)).await {
Ok(Ok(_)) => {
debug!("Connection to {} is active", self.addr);
Ok(true)
@@ -334,9 +299,9 @@ impl Target for WebhookTarget {
async fn save(&self, event: Event) -> Result<(), TargetError> {
if let Some(store) = &self.store {
// Call the store method directly, no longer need to acquire the lock
store.put(event).map_err(|e| {
TargetError::Storage(format!("Failed to save event to store: {}", e))
})?;
store
.put(event)
.map_err(|e| TargetError::Storage(format!("Failed to save event to store: {}", e)))?;
debug!("Event saved to store for target: {}", self.id);
Ok(())
} else {
@@ -373,10 +338,7 @@ impl Target for WebhookTarget {
Ok(event) => event,
Err(StoreError::NotFound) => return Ok(()),
Err(e) => {
return Err(TargetError::Storage(format!(
"Failed to get event from store: {}",
e
)));
return Err(TargetError::Storage(format!("Failed to get event from store: {}", e)));
}
};
@@ -388,23 +350,12 @@ impl Target for WebhookTarget {
}
// Use the immutable reference of the store to delete the event content corresponding to the key
debug!(
"Deleting event from store for target: {}, key:{}, start",
self.id,
key.to_string()
);
debug!("Deleting event from store for target: {}, key:{}, start", self.id, key.to_string());
match store.del(&key) {
Ok(_) => debug!(
"Event deleted from store for target: {}, key:{}, end",
self.id,
key.to_string()
),
Ok(_) => debug!("Event deleted from store for target: {}, key:{}, end", self.id, key.to_string()),
Err(e) => {
error!("Failed to delete event from store: {}", e);
return Err(TargetError::Storage(format!(
"Failed to delete event from store: {}",
e
)));
return Err(TargetError::Storage(format!("Failed to delete event from store: {}", e)));
}
}
@@ -433,10 +384,7 @@ impl Target for WebhookTarget {
async fn init(&self) -> Result<(), TargetError> {
// If the target is disabled, return to success directly
if !self.is_enabled() {
debug!(
"Webhook target {} is disabled, skipping initialization",
self.id
);
debug!("Webhook target {} is disabled, skipping initialization", self.id);
return Ok(());
}