mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-21 20:06:37 +00:00
refactor: narrow storage compatibility surfaces (#3585)
* refactor: narrow storage compatibility surfaces * refactor: narrow observability compatibility surfaces
This commit is contained in:
@@ -13,7 +13,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{
|
||||
Event, NotificationError, registry::TargetRegistry, rule_engine::NotifyRuleEngine, runtime_facade::NotifyRuntimeFacade,
|
||||
Event, NotificationError,
|
||||
registry::TargetRegistry,
|
||||
rule_engine::NotifyRuleEngine,
|
||||
runtime_facade::NotifyRuntimeFacade,
|
||||
storage_compat::{self, NotifyConfigStoreError},
|
||||
};
|
||||
use rustfs_config::notify::{
|
||||
NOTIFY_AMQP_SUB_SYS, NOTIFY_KAFKA_SUB_SYS, NOTIFY_MQTT_SUB_SYS, NOTIFY_MYSQL_SUB_SYS, NOTIFY_NATS_SUB_SYS,
|
||||
@@ -316,17 +320,16 @@ impl NotifyConfigManager {
|
||||
where
|
||||
F: FnMut(&mut Config) -> bool,
|
||||
{
|
||||
let Some(store) = crate::storage_compat::ecstore::global::resolve_object_store_handle() else {
|
||||
return Err(NotificationError::StorageNotAvailable(
|
||||
"Failed to save target configuration: server storage not initialized".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let mut new_config = crate::storage_compat::ecstore::config::com::read_config_without_migrate(store.clone())
|
||||
let Some(new_config) = storage_compat::update_server_config(&mut modifier)
|
||||
.await
|
||||
.map_err(|e| NotificationError::ReadConfig(e.to_string()))?;
|
||||
|
||||
if !modifier(&mut new_config) {
|
||||
.map_err(|err| match err {
|
||||
NotifyConfigStoreError::StorageNotAvailable => NotificationError::StorageNotAvailable(
|
||||
"Failed to save target configuration: server storage not initialized".to_string(),
|
||||
),
|
||||
NotifyConfigStoreError::Read(err) => NotificationError::ReadConfig(err),
|
||||
NotifyConfigStoreError::Save(err) => NotificationError::SaveConfig(err),
|
||||
})?
|
||||
else {
|
||||
debug!(
|
||||
event = EVENT_NOTIFY_CONFIG_UPDATE,
|
||||
component = LOG_COMPONENT_NOTIFY,
|
||||
@@ -336,11 +339,7 @@ impl NotifyConfigManager {
|
||||
"notify config update"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
crate::storage_compat::ecstore::config::com::save_server_config(store, &new_config)
|
||||
.await
|
||||
.map_err(|e| NotificationError::SaveConfig(e.to_string()))?;
|
||||
};
|
||||
|
||||
info!(
|
||||
event = EVENT_NOTIFY_CONFIG_UPDATE,
|
||||
|
||||
+29
-17
@@ -19,8 +19,6 @@ use rustfs_s3_types::{EventName, event_schema_version};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::form_urlencoded;
|
||||
|
||||
use crate::storage_compat::NotifyObjectInfo;
|
||||
|
||||
/// Represents the identity of the user who triggered the event
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -66,6 +64,22 @@ pub struct Object {
|
||||
pub sequencer: String,
|
||||
}
|
||||
|
||||
/// Object metadata required by notification event serialization.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NotifyObjectInfo {
|
||||
pub bucket: String,
|
||||
pub name: String,
|
||||
pub size: i64,
|
||||
pub etag: Option<String>,
|
||||
pub content_type: Option<String>,
|
||||
pub user_defined: HashMap<String, String>,
|
||||
pub version_id: Option<String>,
|
||||
pub mod_time: Option<DateTime<Utc>>,
|
||||
pub restore_expires: Option<DateTime<Utc>>,
|
||||
pub storage_class: Option<String>,
|
||||
pub transitioned_tier: Option<String>,
|
||||
}
|
||||
|
||||
/// Metadata about the event
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -206,7 +220,7 @@ impl Event {
|
||||
pub fn new(args: EventArgs) -> Self {
|
||||
let event_time = Utc::now().naive_local();
|
||||
let sequencer = match args.object.mod_time {
|
||||
Some(t) => format!("{:X}", t.unix_timestamp_nanos()),
|
||||
Some(t) => format!("{:X}", t.timestamp_nanos_opt().unwrap_or(0)),
|
||||
None => format!("{:X}", event_time.and_utc().timestamp_nanos_opt().unwrap_or(0)),
|
||||
};
|
||||
|
||||
@@ -217,10 +231,7 @@ impl Event {
|
||||
let key_name = form_urlencoded::byte_serialize(args.object.name.as_bytes()).collect::<String>();
|
||||
let principal_id = args.req_params.get("principalId").unwrap_or(&String::new()).to_string();
|
||||
|
||||
let version_id = match args.object.version_id {
|
||||
Some(id) => Some(id.to_string()),
|
||||
None => Some(args.version_id.clone()),
|
||||
};
|
||||
let version_id = args.object.version_id.clone().or_else(|| Some(args.version_id.clone()));
|
||||
|
||||
let mut s3_metadata = Metadata {
|
||||
schema_version: "1.0".to_string(),
|
||||
@@ -257,11 +268,12 @@ impl Event {
|
||||
}
|
||||
|
||||
let glacier_event_data = if args.event_name == EventName::ObjectRestoreCompleted {
|
||||
args.object.restore_expires.and_then(|expiry| {
|
||||
let expiry_time = DateTime::<Utc>::from_timestamp(expiry.unix_timestamp(), expiry.nanosecond())?;
|
||||
let storage_class = args.object.storage_class.clone().or_else(|| {
|
||||
(!args.object.transitioned_object.tier.is_empty()).then_some(args.object.transitioned_object.tier.clone())
|
||||
})?;
|
||||
args.object.restore_expires.and_then(|expiry_time| {
|
||||
let storage_class = args
|
||||
.object
|
||||
.storage_class
|
||||
.clone()
|
||||
.or_else(|| args.object.transitioned_tier.clone())?;
|
||||
Some(GlacierEventData {
|
||||
restore_event_data: RestoreEventData {
|
||||
lifecycle_restoration_expiry_time: expiry_time.to_rfc3339_opts(SecondsFormat::Millis, true),
|
||||
@@ -367,11 +379,11 @@ pub struct EventArgsBuilder {
|
||||
|
||||
impl EventArgsBuilder {
|
||||
/// Creates a new builder with the required fields.
|
||||
pub fn new(event_name: EventName, bucket_name: impl Into<String>, object: NotifyObjectInfo) -> Self {
|
||||
pub fn new(event_name: EventName, bucket_name: impl Into<String>, object: impl Into<NotifyObjectInfo>) -> Self {
|
||||
Self {
|
||||
event_name,
|
||||
bucket_name: bucket_name.into(),
|
||||
object,
|
||||
object: object.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -389,8 +401,8 @@ impl EventArgsBuilder {
|
||||
}
|
||||
|
||||
/// Sets the object information.
|
||||
pub fn object(mut self, object: NotifyObjectInfo) -> Self {
|
||||
self.object = object;
|
||||
pub fn object(mut self, object: impl Into<NotifyObjectInfo>) -> Self {
|
||||
self.object = object.into();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -503,7 +515,7 @@ mod tests {
|
||||
NotifyObjectInfo {
|
||||
bucket: "bucket".to_string(),
|
||||
name: "key".to_string(),
|
||||
restore_expires: Some(time::OffsetDateTime::from_unix_timestamp(1_700_000_000).unwrap()),
|
||||
restore_expires: DateTime::<Utc>::from_timestamp(1_700_000_000, 0),
|
||||
storage_class: Some("GLACIER".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -41,7 +41,7 @@ mod storage_compat;
|
||||
pub use bucket_config_manager::NotifyBucketConfigManager;
|
||||
pub use config_manager::{NotifyConfigManager, runtime_target_id_for_subsystem};
|
||||
pub use error::{LifecycleError, NotificationError};
|
||||
pub use event::{Event, EventArgs, EventArgsBuilder};
|
||||
pub use event::{Event, EventArgs, EventArgsBuilder, NotifyObjectInfo};
|
||||
pub use event_bridge::{LiveEventHistory, NotifyEventBridge};
|
||||
pub use global::{
|
||||
initialize, initialize_live_events, is_notification_system_initialized, notification_metrics_snapshot, notification_system,
|
||||
@@ -55,4 +55,3 @@ pub use runtime_facade::NotifyRuntimeFacade;
|
||||
pub use runtime_view::NotifyRuntimeView;
|
||||
pub use services::NotifyServices;
|
||||
pub use status_view::NotifyStatusView;
|
||||
pub use storage_compat::NotifyObjectInfo;
|
||||
|
||||
@@ -12,8 +12,110 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub(crate) mod ecstore {
|
||||
pub(crate) use rustfs_ecstore::{config, global};
|
||||
use chrono::{DateTime, Utc};
|
||||
use rustfs_config::server_config::Config;
|
||||
use rustfs_ecstore::{config, global};
|
||||
|
||||
use crate::event::NotifyObjectInfo;
|
||||
|
||||
type EcstoreObjectInfo = rustfs_ecstore::store_api::ObjectInfo;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum NotifyConfigStoreError {
|
||||
StorageNotAvailable,
|
||||
Read(String),
|
||||
Save(String),
|
||||
}
|
||||
|
||||
pub type NotifyObjectInfo = rustfs_ecstore::store_api::ObjectInfo;
|
||||
pub(crate) async fn update_server_config<F>(mut modifier: F) -> Result<Option<Config>, NotifyConfigStoreError>
|
||||
where
|
||||
F: FnMut(&mut Config) -> bool,
|
||||
{
|
||||
let Some(store) = global::resolve_object_store_handle() else {
|
||||
return Err(NotifyConfigStoreError::StorageNotAvailable);
|
||||
};
|
||||
|
||||
let mut new_config = config::com::read_config_without_migrate(store.clone())
|
||||
.await
|
||||
.map_err(|err| NotifyConfigStoreError::Read(err.to_string()))?;
|
||||
|
||||
if !modifier(&mut new_config) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
config::com::save_server_config(store, &new_config)
|
||||
.await
|
||||
.map_err(|err| NotifyConfigStoreError::Save(err.to_string()))?;
|
||||
|
||||
Ok(Some(new_config))
|
||||
}
|
||||
|
||||
impl From<EcstoreObjectInfo> for NotifyObjectInfo {
|
||||
fn from(object: EcstoreObjectInfo) -> Self {
|
||||
Self {
|
||||
bucket: object.bucket,
|
||||
name: object.name,
|
||||
size: object.size,
|
||||
etag: object.etag,
|
||||
content_type: object.content_type,
|
||||
user_defined: object
|
||||
.user_defined
|
||||
.iter()
|
||||
.map(|(key, value)| (key.clone(), value.clone()))
|
||||
.collect(),
|
||||
version_id: object.version_id.map(|version_id| version_id.to_string()),
|
||||
mod_time: object
|
||||
.mod_time
|
||||
.and_then(|value| DateTime::<Utc>::from_timestamp(value.unix_timestamp(), value.nanosecond())),
|
||||
restore_expires: object
|
||||
.restore_expires
|
||||
.and_then(|value| DateTime::<Utc>::from_timestamp(value.unix_timestamp(), value.nanosecond())),
|
||||
storage_class: object.storage_class,
|
||||
transitioned_tier: (!object.transitioned_object.tier.is_empty()).then_some(object.transitioned_object.tier),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rustfs_ecstore::bucket::lifecycle::bucket_lifecycle_ops::TransitionedObject;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
#[test]
|
||||
fn ecstore_object_info_conversion_preserves_notify_event_fields() {
|
||||
let mod_time = OffsetDateTime::UNIX_EPOCH + Duration::seconds(42);
|
||||
let restore_expires = OffsetDateTime::UNIX_EPOCH + Duration::seconds(1_700_000_000);
|
||||
let mut metadata = HashMap::new();
|
||||
metadata.insert("x-amz-meta-key".to_string(), "value".to_string());
|
||||
|
||||
let converted = NotifyObjectInfo::from(EcstoreObjectInfo {
|
||||
bucket: "bucket".to_string(),
|
||||
name: "object".to_string(),
|
||||
size: 123,
|
||||
etag: Some("etag".to_string()),
|
||||
content_type: Some("text/plain".to_string()),
|
||||
user_defined: Arc::new(metadata),
|
||||
mod_time: Some(mod_time),
|
||||
restore_expires: Some(restore_expires),
|
||||
storage_class: Some("GLACIER".to_string()),
|
||||
transitioned_object: TransitionedObject {
|
||||
tier: "DEEP_ARCHIVE".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
assert_eq!(converted.bucket, "bucket");
|
||||
assert_eq!(converted.name, "object");
|
||||
assert_eq!(converted.size, 123);
|
||||
assert_eq!(converted.etag.as_deref(), Some("etag"));
|
||||
assert_eq!(converted.content_type.as_deref(), Some("text/plain"));
|
||||
assert_eq!(converted.user_defined.get("x-amz-meta-key").map(String::as_str), Some("value"));
|
||||
assert_eq!(converted.mod_time, DateTime::<Utc>::from_timestamp(42, 0));
|
||||
assert_eq!(converted.restore_expires, DateTime::<Utc>::from_timestamp(1_700_000_000, 0));
|
||||
assert_eq!(converted.storage_class.as_deref(), Some("GLACIER"));
|
||||
assert_eq!(converted.transitioned_tier.as_deref(), Some("DEEP_ARCHIVE"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user