From c28fee00136f71b27159a55cd3fb71bdd55ebed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Thu, 18 Jun 2026 22:42:02 +0800 Subject: [PATCH] refactor: continue storage api contract cleanup (#3580) * refactor: move delete object contracts to storage api * refactor: narrow store api compatibility exports * refactor: route table catalog test through storage compat --- Cargo.lock | 1 + .../bucket/lifecycle/bucket_lifecycle_ops.rs | 14 +- .../bucket/replication/replication_pool.rs | 5 +- .../replication/replication_resyncer.rs | 7 +- .../src/client/object_handlers_common.rs | 4 +- crates/ecstore/src/set_disk.rs | 11 +- crates/ecstore/src/sets.rs | 9 +- crates/ecstore/src/store.rs | 6 +- crates/ecstore/src/store_api/traits.rs | 5 +- crates/ecstore/src/store_api/types.rs | 55 +------- crates/heal/src/heal/storage_compat.rs | 6 +- crates/iam/src/store/storage_compat.rs | 4 +- crates/notify/src/storage_compat.rs | 6 +- crates/protocols/src/swift/storage_compat.rs | 6 +- crates/s3select-api/src/storage_compat.rs | 6 +- crates/scanner/src/storage_compat.rs | 12 +- crates/storage-api/Cargo.toml | 1 + crates/storage-api/src/lib.rs | 1 + crates/storage-api/src/object.rs | 83 ++++++++++++ docs/architecture/migration-progress.md | 127 ++++++++++++++++-- rustfs/src/storage/mod.rs | 4 +- rustfs/src/storage/storage_compat.rs | 6 +- rustfs/src/table_catalog.rs | 4 +- scripts/check_architecture_migration_rules.sh | 40 ++++++ 24 files changed, 310 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fcaf4384b..485ff48a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10065,6 +10065,7 @@ name = "rustfs-storage-api" version = "1.0.0-beta.8" dependencies = [ "async-trait", + "rustfs-filemeta", "serde", "serde_json", "time", diff --git a/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs b/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs index a70f71b83..d473c91ce 100644 --- a/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs +++ b/crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs @@ -37,7 +37,7 @@ use crate::global::GLOBAL_LocalNodeName; use crate::global::{GLOBAL_LifecycleSys, GLOBAL_TierConfigMgr, get_global_deployment_id}; use crate::set_disk::{MAX_PARTS_COUNT, RUSTFS_MULTIPART_BUCKET_KEY, RUSTFS_MULTIPART_OBJECT_KEY, SetDisks}; use crate::store::ECStore; -use crate::store_api::{GetObjectReader, ObjectInfo, ObjectOptions, ObjectToDelete}; +use crate::store_api::{GetObjectReader, ObjectInfo, ObjectOptions}; use crate::tier::warm_backend::WarmBackendGetOpts; use async_channel::{Receiver as A_Receiver, Sender as A_Sender, bounded}; use futures::Future; @@ -58,7 +58,9 @@ use rustfs_filemeta::{ VersionPurgeStatusType, get_file_info, is_restored_object_on_disk, }; use rustfs_s3_types::EventName; -use rustfs_storage_api::{HTTPRangeSpec, ListOperations as _, MultipartOperations as _, ObjectOperations as _}; +use rustfs_storage_api::{ + DeletedObject, HTTPRangeSpec, ListOperations as _, MultipartOperations as _, ObjectOperations as _, ObjectToDelete, +}; 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, @@ -2692,9 +2694,9 @@ pub async fn apply_expiry_rule(event: &lifecycle::Event, src: &LcEventSrc, oi: & expiry_state.enqueue_by_days(oi, event, src).await } -fn lifecycle_deleted_object(oi: &ObjectInfo, dobj: &ObjectInfo) -> crate::store_api::DeletedObject { +fn lifecycle_deleted_object(oi: &ObjectInfo, dobj: &ObjectInfo) -> DeletedObject { if dobj.delete_marker { - return crate::store_api::DeletedObject { + return DeletedObject { object_name: oi.name.clone(), delete_marker: true, delete_marker_version_id: dobj.version_id, @@ -2704,7 +2706,7 @@ fn lifecycle_deleted_object(oi: &ObjectInfo, dobj: &ObjectInfo) -> crate::store_ } if oi.delete_marker && oi.version_id.is_some() { - return crate::store_api::DeletedObject { + return DeletedObject { object_name: oi.name.clone(), delete_marker: false, delete_marker_version_id: oi.version_id, @@ -2713,7 +2715,7 @@ fn lifecycle_deleted_object(oi: &ObjectInfo, dobj: &ObjectInfo) -> crate::store_ }; } - crate::store_api::DeletedObject { + DeletedObject { object_name: oi.name.clone(), delete_marker: false, version_id: oi.version_id, diff --git a/crates/ecstore/src/bucket/replication/replication_pool.rs b/crates/ecstore/src/bucket/replication/replication_pool.rs index 9e7388033..2d78aa2c9 100644 --- a/crates/ecstore/src/bucket/replication/replication_pool.rs +++ b/crates/ecstore/src/bucket/replication/replication_pool.rs @@ -42,6 +42,7 @@ use rustfs_filemeta::VersionPurgeStatusType; use rustfs_filemeta::replication_statuses_map; use rustfs_filemeta::version_purge_statuses_map; use rustfs_filemeta::{REPLICATE_EXISTING, REPLICATE_HEAL, REPLICATE_HEAL_DELETE}; +use rustfs_storage_api::DeletedObject; use rustfs_utils::http::{SUFFIX_REPLICATION_TIMESTAMP, get_str}; use std::any::Any; use std::sync::Arc; @@ -838,7 +839,7 @@ impl ReplicationPool { // get_object_info here because the delete-marker or version may // already be absent from the local store — that is expected. let dv = DeletedObjectReplicationInfo { - delete_object: crate::store_api::DeletedObject { + delete_object: DeletedObject { object_name: entry.object.clone(), version_id: entry.version_id, delete_marker_version_id: entry.delete_marker_version_id, @@ -1577,7 +1578,7 @@ pub async fn queue_replication_heal_internal( }; let dv = DeletedObjectReplicationInfo { - delete_object: crate::store_api::DeletedObject { + delete_object: DeletedObject { object_name: roi.name.clone(), delete_marker_version_id: dm_version_id, version_id, diff --git a/crates/ecstore/src/bucket/replication/replication_resyncer.rs b/crates/ecstore/src/bucket/replication/replication_resyncer.rs index 747de6e14..5e596c8e0 100644 --- a/crates/ecstore/src/bucket/replication/replication_resyncer.rs +++ b/crates/ecstore/src/bucket/replication/replication_resyncer.rs @@ -33,10 +33,7 @@ use crate::global::GLOBAL_LocalNodeName; use crate::global::get_global_bucket_monitor; use crate::resolve_object_store_handle; use crate::set_disk::get_lock_acquire_timeout; -use crate::store_api::{ - DeletedObject, ListOperations, NamespaceLocking, ObjectIO, ObjectInfo, ObjectOperations, ObjectOptions, ObjectToDelete, - WalkOptions, -}; +use crate::store_api::{ListOperations, NamespaceLocking, ObjectIO, ObjectInfo, ObjectOperations, ObjectOptions, WalkOptions}; use aws_sdk_s3::error::{ProvideErrorMetadata, SdkError}; use aws_sdk_s3::operation::head_object::{HeadObjectError, HeadObjectOutput}; use aws_sdk_s3::primitives::ByteStream; @@ -61,7 +58,7 @@ use rustfs_filemeta::{ get_replication_state, parse_replicate_decision, replication_statuses_map, target_reset_header, version_purge_statuses_map, }; use rustfs_s3_types::EventName; -use rustfs_storage_api::HTTPRangeSpec; +use rustfs_storage_api::{DeletedObject, HTTPRangeSpec, ObjectToDelete}; 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/client/object_handlers_common.rs b/crates/ecstore/src/client/object_handlers_common.rs index e840ac7fc..8a470a815 100644 --- a/crates/ecstore/src/client/object_handlers_common.rs +++ b/crates/ecstore/src/client/object_handlers_common.rs @@ -25,10 +25,10 @@ use crate::bucket::replication::{DeletedObjectReplicationInfo, check_replicate_d use crate::bucket::versioning::VersioningApi; use crate::bucket::versioning_sys::BucketVersioningSys; use crate::store::ECStore; -use crate::store_api::{ObjectOptions, ObjectToDelete}; +use crate::store_api::ObjectOptions; use rustfs_filemeta::{REPLICATE_INCOMING_DELETE, ReplicationState, version_purge_statuses_map}; use rustfs_lock::MAX_DELETE_LIST; -use rustfs_storage_api::ObjectOperations as _; +use rustfs_storage_api::{ObjectOperations as _, ObjectToDelete}; fn lifecycle_version_delete_replication_state( replicate_decision_str: String, diff --git a/crates/ecstore/src/set_disk.rs b/crates/ecstore/src/set_disk.rs index de06b3d82..b461541b2 100644 --- a/crates/ecstore/src/set_disk.rs +++ b/crates/ecstore/src/set_disk.rs @@ -34,8 +34,8 @@ use crate::error::{Error, Result, is_err_version_not_found}; use crate::error::{GenericError, ObjectApiError, is_err_object_not_found}; use crate::global::{GLOBAL_LocalNodeName, GLOBAL_TierConfigMgr}; use crate::store_api::ListObjectVersionsInfo; +use crate::store_api::ObjectOptions; use crate::store_api::{ObjectInfoOrErr, WalkOptions}; -use crate::store_api::{ObjectOptions, ObjectToDelete}; use crate::{ bucket::lifecycle::bucket_lifecycle_ops::{ LifecycleOps, gen_transition_objname, get_transitioned_object_reader, put_restore_opts, @@ -51,10 +51,7 @@ use crate::{ // event::name::EventName, event_notification::{EventArgs, send_event}, global::{GLOBAL_LOCAL_DISK_MAP, GLOBAL_LOCAL_DISK_SET_DRIVES, get_global_deployment_id, is_dist_erasure}, - store_api::{ - DeletedObject, GetObjectReader, ListObjectsV2Info, MultipartOperations, ObjectIO, ObjectInfo, ObjectOperations, - PutObjReader, - }, + store_api::{GetObjectReader, ListObjectsV2Info, MultipartOperations, ObjectIO, ObjectInfo, ObjectOperations, PutObjReader}, store_init::load_format_erasure, }; use bytes::Bytes; @@ -87,8 +84,8 @@ use rustfs_object_capacity::capacity_scope::{ use rustfs_s3_types::EventName; use rustfs_storage_api::HTTPRangeSpec; use rustfs_storage_api::{ - BucketInfo, BucketOperations, BucketOptions, CompletePart, DeleteBucketOptions, ListMultipartsInfo, ListPartsInfo, - MakeBucketOptions, MultipartInfo, MultipartUploadResult, PartInfo, + BucketInfo, BucketOperations, BucketOptions, CompletePart, DeleteBucketOptions, DeletedObject, ListMultipartsInfo, + ListPartsInfo, MakeBucketOptions, MultipartInfo, MultipartUploadResult, ObjectToDelete, PartInfo, }; use rustfs_storage_api::{MultipartOperations as _, NamespaceLocking as _, ObjectIO as _, ObjectOperations as _}; use rustfs_utils::http::headers::AMZ_OBJECT_TAGGING; diff --git a/crates/ecstore/src/sets.rs b/crates/ecstore/src/sets.rs index ed79f76eb..5bcd9a0c9 100644 --- a/crates/ecstore/src/sets.rs +++ b/crates/ecstore/src/sets.rs @@ -27,10 +27,7 @@ use crate::{ error::StorageError, global::{GLOBAL_LOCAL_DISK_SET_DRIVES, get_global_lock_clients, is_dist_erasure}, set_disk::SetDisks, - store_api::{ - DeletedObject, GetObjectReader, ListObjectVersionsInfo, ListObjectsV2Info, ObjectInfo, ObjectOptions, ObjectToDelete, - PutObjReader, - }, + store_api::{GetObjectReader, ListObjectVersionsInfo, ListObjectsV2Info, ObjectInfo, ObjectOptions, PutObjReader}, store_init::{check_format_erasure_values, get_format_erasure_in_quorum, load_format_erasure_all, save_format_file}, }; use futures::{ @@ -50,8 +47,8 @@ use rustfs_madmin::heal_commands::{HealDriveInfo, HealResultItem}; use rustfs_storage_api::CompletePart; use rustfs_storage_api::HTTPRangeSpec; use rustfs_storage_api::{ - BucketInfo, BucketOperations, BucketOptions, DeleteBucketOptions, ListMultipartsInfo, ListPartsInfo, MakeBucketOptions, - MultipartInfo, MultipartUploadResult, PartInfo, + BucketInfo, BucketOperations, BucketOptions, DeleteBucketOptions, DeletedObject, ListMultipartsInfo, ListPartsInfo, + MakeBucketOptions, MultipartInfo, MultipartUploadResult, ObjectToDelete, PartInfo, }; use rustfs_storage_api::{ObjectIO as _, ObjectOperations as _}; use rustfs_utils::{crc_hash, path::path_join_buf, sip_hash}; diff --git a/crates/ecstore/src/store.rs b/crates/ecstore/src/store.rs index c1960030e..9066cce19 100644 --- a/crates/ecstore/src/store.rs +++ b/crates/ecstore/src/store.rs @@ -62,7 +62,7 @@ use crate::{ endpoints::EndpointServerPools, rpc::S3PeerSys, sets::Sets, - store_api::{DeletedObject, GetObjectReader, ListObjectsV2Info, ObjectInfo, ObjectOptions, ObjectToDelete, PutObjReader}, + store_api::{GetObjectReader, ListObjectsV2Info, ObjectInfo, ObjectOptions, PutObjReader}, store_init, }; use futures::future::join_all; @@ -77,8 +77,8 @@ use rustfs_lock::{LocalClient, LockClient, NamespaceLockWrapper}; use rustfs_madmin::heal_commands::HealResultItem; use rustfs_storage_api::HTTPRangeSpec; use rustfs_storage_api::{ - BucketInfo, BucketOperations, BucketOptions, CompletePart, DeleteBucketOptions, ListMultipartsInfo, ListPartsInfo, - MakeBucketOptions, MultipartInfo, MultipartUploadResult, PartInfo, + BucketInfo, BucketOperations, BucketOptions, CompletePart, DeleteBucketOptions, DeletedObject, ListMultipartsInfo, + ListPartsInfo, MakeBucketOptions, MultipartInfo, MultipartUploadResult, ObjectToDelete, PartInfo, }; use rustfs_utils::path::{decode_dir_object, encode_dir_object, path_join_buf}; use s3s::dto::{BucketVersioningStatus, ObjectLockConfiguration, ObjectLockEnabled, VersioningConfiguration}; diff --git a/crates/ecstore/src/store_api/traits.rs b/crates/ecstore/src/store_api/traits.rs index 5fd75a167..be9fe647a 100644 --- a/crates/ecstore/src/store_api/traits.rs +++ b/crates/ecstore/src/store_api/traits.rs @@ -1,5 +1,8 @@ use super::*; -use rustfs_storage_api::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo}; +use rustfs_storage_api::{ + CompletePart, DeletedObject, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, ObjectToDelete, + PartInfo, +}; pub trait ObjectIO: rustfs_storage_api::ObjectIO< diff --git a/crates/ecstore/src/store_api/types.rs b/crates/ecstore/src/store_api/types.rs index 7421fa91f..d7b145482 100644 --- a/crates/ecstore/src/store_api/types.rs +++ b/crates/ecstore/src/store_api/types.rs @@ -9,6 +9,8 @@ pub type ListObjectsV2Info = rustfs_storage_api::ListObjectsV2Info; pub type ListObjectVersionsInfo = rustfs_storage_api::ListObjectVersionsInfo; pub type ObjectInfoOrErr = rustfs_storage_api::ObjectInfoOrErr; pub type WalkOptions = rustfs_storage_api::WalkOptions; +pub type ObjectToDelete = rustfs_storage_api::ObjectToDelete; +pub type DeletedObject = rustfs_storage_api::DeletedObject; #[derive(Debug, Default, Clone)] pub struct ObjectOptions { @@ -714,59 +716,6 @@ fn versions_after_marker(file_infos: &rustfs_filemeta::FileInfoVersions, marker: .unwrap_or(&file_infos.versions) } -#[derive(Debug, Default, Clone)] -pub struct ObjectToDelete { - pub object_name: String, - pub version_id: Option, - pub delete_marker_replication_status: Option, - pub version_purge_status: Option, - pub version_purge_statuses: Option, - pub replicate_decision_str: Option, -} - -impl ObjectToDelete { - pub fn replication_state(&self) -> ReplicationState { - ReplicationState { - replication_status_internal: self.delete_marker_replication_status.clone(), - version_purge_status_internal: self.version_purge_statuses.clone(), - replicate_decision_str: self.replicate_decision_str.clone().unwrap_or_default(), - targets: replication_statuses_map(self.delete_marker_replication_status.as_deref().unwrap_or_default()), - purge_targets: version_purge_statuses_map(self.version_purge_statuses.as_deref().unwrap_or_default()), - ..Default::default() - } - } -} - -#[derive(Debug, Default, Clone)] -pub struct DeletedObject { - pub delete_marker: bool, - pub delete_marker_version_id: Option, - pub object_name: String, - pub version_id: Option, - // MTime of DeleteMarker on source that needs to be propagated to replica - pub delete_marker_mtime: Option, - // to support delete marker replication - pub replication_state: Option, - pub found: bool, - pub force_delete: bool, -} - -impl DeletedObject { - pub fn version_purge_status(&self) -> VersionPurgeStatusType { - self.replication_state - .as_ref() - .map(|v| v.composite_version_purge_status()) - .unwrap_or(VersionPurgeStatusType::Empty) - } - - pub fn delete_marker_replication_status(&self) -> ReplicationStatusType { - self.replication_state - .as_ref() - .map(|v| v.composite_replication_status()) - .unwrap_or(ReplicationStatusType::Empty) - } -} - type WalkFilter = fn(&FileInfo) -> bool; #[cfg(test)] diff --git a/crates/heal/src/heal/storage_compat.rs b/crates/heal/src/heal/storage_compat.rs index 265f1b9dc..aae50e94c 100644 --- a/crates/heal/src/heal/storage_compat.rs +++ b/crates/heal/src/heal/storage_compat.rs @@ -13,7 +13,11 @@ // limitations under the License. pub(crate) mod ecstore { - pub(crate) use rustfs_ecstore::{data_usage, disk, error, global, store, store_api}; + pub(crate) use rustfs_ecstore::{data_usage, disk, error, global, store}; + + pub(crate) mod store_api { + pub(crate) use rustfs_ecstore::store_api::{ObjectInfo, ObjectOptions, PutObjReader}; + } } pub(crate) use self::ecstore::{ data_usage::DATA_USAGE_CACHE_NAME, diff --git a/crates/iam/src/store/storage_compat.rs b/crates/iam/src/store/storage_compat.rs index d9e06d022..38df459a6 100644 --- a/crates/iam/src/store/storage_compat.rs +++ b/crates/iam/src/store/storage_compat.rs @@ -13,7 +13,9 @@ // limitations under the License. mod ecstore { - pub(super) use rustfs_ecstore::store_api; + pub(super) mod store_api { + pub(crate) use rustfs_ecstore::store_api::{ObjectInfo, ObjectOptions}; + } } pub(super) type IamObjectInfo = ecstore::store_api::ObjectInfo; diff --git a/crates/notify/src/storage_compat.rs b/crates/notify/src/storage_compat.rs index 573e9df05..d3147bafa 100644 --- a/crates/notify/src/storage_compat.rs +++ b/crates/notify/src/storage_compat.rs @@ -13,7 +13,11 @@ // limitations under the License. pub(crate) mod ecstore { - pub(crate) use rustfs_ecstore::{config, global, store_api}; + pub(crate) use rustfs_ecstore::{config, global}; + + pub(crate) mod store_api { + pub(crate) use rustfs_ecstore::store_api::ObjectInfo; + } } pub type NotifyObjectInfo = ecstore::store_api::ObjectInfo; diff --git a/crates/protocols/src/swift/storage_compat.rs b/crates/protocols/src/swift/storage_compat.rs index c71fb6fe3..34f5c7555 100644 --- a/crates/protocols/src/swift/storage_compat.rs +++ b/crates/protocols/src/swift/storage_compat.rs @@ -13,7 +13,11 @@ // limitations under the License. mod ecstore { - pub(super) use rustfs_ecstore::{bucket, error, resolve_object_store_handle, store, store_api}; + pub(super) use rustfs_ecstore::{bucket, error, resolve_object_store_handle, store}; + + pub(super) mod store_api { + pub(crate) use rustfs_ecstore::store_api::{GetObjectReader, ObjectInfo, ObjectOptions, PutObjReader}; + } } use self::ecstore::{ bucket::{metadata::BucketMetadata, metadata_sys}, diff --git a/crates/s3select-api/src/storage_compat.rs b/crates/s3select-api/src/storage_compat.rs index c7a591414..009e0fe41 100644 --- a/crates/s3select-api/src/storage_compat.rs +++ b/crates/s3select-api/src/storage_compat.rs @@ -13,7 +13,11 @@ // limitations under the License. pub(crate) mod ecstore { - pub(crate) use rustfs_ecstore::{error, resolve_object_store_handle, set_disk, store, store_api}; + pub(crate) use rustfs_ecstore::{error, resolve_object_store_handle, set_disk, store}; + + pub(crate) mod store_api { + pub(crate) use rustfs_ecstore::store_api::{GetObjectReader, ObjectInfo, ObjectOptions}; + } } use ecstore::store_api::{ diff --git a/crates/scanner/src/storage_compat.rs b/crates/scanner/src/storage_compat.rs index aaf2302ab..709e45ba2 100644 --- a/crates/scanner/src/storage_compat.rs +++ b/crates/scanner/src/storage_compat.rs @@ -16,8 +16,12 @@ use http::HeaderMap; pub(crate) mod ecstore { pub(crate) use rustfs_ecstore::{ bucket, cache_value, config, data_usage, disk, error, global, pools, resolve_object_store_handle, set_disk, store, - store_api, store_utils, + store_utils, }; + + pub(crate) mod store_api { + pub(crate) use rustfs_ecstore::store_api::{GetObjectReader, ObjectInfo, ObjectOptions, PutObjReader}; + } } pub(crate) use self::ecstore::{ bucket::{ @@ -49,11 +53,11 @@ pub(crate) use self::ecstore::{ store::ECStore, store_api::{ GetObjectReader as EcstoreGetObjectReader, ObjectInfo as EcstoreObjectInfo, ObjectOptions as EcstoreObjectOptions, - ObjectToDelete as EcstoreObjectToDelete, PutObjReader as EcstorePutObjReader, + PutObjReader as EcstorePutObjReader, }, store_utils::is_reserved_or_invalid_bucket, }; -use rustfs_storage_api::{HTTPRangeSpec, ObjectIO}; +use rustfs_storage_api::{HTTPRangeSpec, ObjectIO, ObjectToDelete}; use std::sync::Arc; #[cfg(test)] @@ -65,7 +69,7 @@ pub(crate) use self::ecstore::{ pub type ScannerGetObjectReader = EcstoreGetObjectReader; pub type ScannerObjectInfo = EcstoreObjectInfo; pub type ScannerObjectOptions = EcstoreObjectOptions; -pub type ScannerObjectToDelete = EcstoreObjectToDelete; +pub type ScannerObjectToDelete = ObjectToDelete; pub type ScannerPutObjReader = EcstorePutObjReader; pub(crate) fn resolve_scanner_object_store_handle() -> Option> { diff --git a/crates/storage-api/Cargo.toml b/crates/storage-api/Cargo.toml index d02291886..26dc25e93 100644 --- a/crates/storage-api/Cargo.toml +++ b/crates/storage-api/Cargo.toml @@ -29,6 +29,7 @@ doctest = false [dependencies] async-trait.workspace = true +rustfs-filemeta.workspace = true serde.workspace = true time.workspace = true uuid.workspace = true diff --git a/crates/storage-api/src/lib.rs b/crates/storage-api/src/lib.rs index 7207ba3cc..8c786b48a 100644 --- a/crates/storage-api/src/lib.rs +++ b/crates/storage-api/src/lib.rs @@ -24,6 +24,7 @@ pub use admin::{DiskSetSelector, StorageAdminApi}; pub use bucket::{BucketInfo, BucketOperations, BucketOptions, DeleteBucketOptions, MakeBucketOptions, SRBucketDeleteOp}; pub use error::{StorageErrorCode, StorageResult}; pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo}; +pub use object::{DeletedObject, ObjectToDelete}; pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions}; pub use object::{HealOperations, MultipartOperations, NamespaceLocking, ObjectIO, ObjectOperations}; pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ListOperations, ObjectInfoOrErr}; diff --git a/crates/storage-api/src/object.rs b/crates/storage-api/src/object.rs index ecae2be95..80ddbc058 100644 --- a/crates/storage-api/src/object.rs +++ b/crates/storage-api/src/object.rs @@ -14,6 +14,10 @@ use std::fmt; use std::sync::Arc; + +use rustfs_filemeta::{ + ReplicationState, ReplicationStatusType, VersionPurgeStatusType, replication_statuses_map, version_purge_statuses_map, +}; use time::OffsetDateTime; use uuid::Uuid; @@ -155,6 +159,57 @@ pub enum WalkVersionsSortOrder { Descending, } +#[derive(Debug, Default, Clone)] +pub struct ObjectToDelete { + pub object_name: String, + pub version_id: Option, + pub delete_marker_replication_status: Option, + pub version_purge_status: Option, + pub version_purge_statuses: Option, + pub replicate_decision_str: Option, +} + +impl ObjectToDelete { + pub fn replication_state(&self) -> ReplicationState { + ReplicationState { + replication_status_internal: self.delete_marker_replication_status.clone(), + version_purge_status_internal: self.version_purge_statuses.clone(), + replicate_decision_str: self.replicate_decision_str.clone().unwrap_or_default(), + targets: replication_statuses_map(self.delete_marker_replication_status.as_deref().unwrap_or_default()), + purge_targets: version_purge_statuses_map(self.version_purge_statuses.as_deref().unwrap_or_default()), + ..Default::default() + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct DeletedObject { + pub delete_marker: bool, + pub delete_marker_version_id: Option, + pub object_name: String, + pub version_id: Option, + pub delete_marker_mtime: Option, + pub replication_state: Option, + pub found: bool, + pub force_delete: bool, +} + +impl DeletedObject { + pub fn version_purge_status(&self) -> VersionPurgeStatusType { + self.replication_state + .as_ref() + .map(|v| v.composite_version_purge_status()) + .unwrap_or(VersionPurgeStatusType::Empty) + } + + pub fn delete_marker_replication_status(&self) -> ReplicationStatusType { + self.replication_state + .as_ref() + .map(|v| v.composite_replication_status()) + .unwrap_or(ReplicationStatusType::Empty) + } +} + #[derive(Clone)] pub struct WalkOptions { pub filter: Option, @@ -714,6 +769,34 @@ mod tests { assert!(!opts.include_free_versions); } + #[test] + fn object_to_delete_builds_replication_state() { + let object = ObjectToDelete { + object_name: "photo.jpg".to_owned(), + delete_marker_replication_status: Some("PENDING".to_owned()), + version_purge_statuses: Some("arn:minio:replication:::target=PENDING".to_owned()), + replicate_decision_str: Some("arn:minio:replication:::target=PENDING".to_owned()), + ..Default::default() + }; + + let state = object.replication_state(); + + assert_eq!(state.replication_status_internal.as_deref(), Some("PENDING")); + assert_eq!( + state.version_purge_status_internal.as_deref(), + Some("arn:minio:replication:::target=PENDING") + ); + assert_eq!(state.replicate_decision_str, "arn:minio:replication:::target=PENDING"); + } + + #[test] + fn deleted_object_status_helpers_default_to_empty() { + let object = DeletedObject::default(); + + assert_eq!(object.version_purge_status(), VersionPurgeStatusType::Empty); + assert_eq!(object.delete_marker_replication_status(), ReplicationStatusType::Empty); + } + #[derive(Debug)] struct TestListBackend; diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md index 41abe4079..cf12266a5 100644 --- a/docs/architecture/migration-progress.md +++ b/docs/architecture/migration-progress.md @@ -5,18 +5,18 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block ## Current Context - Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660) -- Branch: `overtrue/arch-compat-reexport-prune` -- Baseline: `origin/main` after `rustfs/rustfs#3578` - (`f3fcdd4ba26181e3fe5d3afb6d6f5510bb9ad186`). +- Branch: `overtrue/arch-delete-object-contracts` +- Baseline: stacked on `rustfs/rustfs#3579` head + (`903aff047d2faffaec907637d64440c84053f62e`). - PR type for this branch: `consumer-migration` - Runtime behavior changes: no migration behavior change expected. -- Rust code changes: prune unused ECStore compatibility re-export allowances - from production and fuzz compatibility boundaries, keep target-specific test - harness exceptions explicit, and gate test-only RustFS storage compatibility - re-exports with `cfg(test)`. -- CI/script changes: add a migration guard rejecting production - `storage_compat.rs` unused-import allows. -- Docs changes: record the compatibility re-export pruning slice. +- Rust code changes: move delete-object DTO contracts from ECStore store_api to + rustfs-storage-api, keep ECStore old-path type aliases, and migrate RustFS, + scanner, and ECStore internal consumers to the storage-api contracts. +- CI/script changes: add a migration guard rejecting reintroduced ECStore + delete DTO definitions, public re-exports, or internal old-path consumers. +- Docs changes: record the delete-object contract move and consumer cleanup + slices. ## Phase 0 Tasks @@ -987,6 +987,68 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block layer guards, formatting check, diff hygiene, risk scan, full pre-commit, and required three-expert review passed. +- [x] `API-036` Move delete-object DTO contracts. + - Current branch: `overtrue/arch-delete-object-contracts`. + - Current slice: move `ObjectToDelete` and `DeletedObject` from ECStore + `store_api` into `rustfs-storage-api`, keep old ECStore paths as type + aliases for compatibility, migrate RustFS/scanner aliases to the + storage-api contracts, and guard against reintroducing ECStore-owned delete + DTO definitions. + - Acceptance: storage-api exports delete-object DTO contracts, ECStore keeps + compatibility type aliases without owning the definitions, external + RustFS/scanner aliases consume storage-api directly, and migration rules + reject restoring ECStore definitions or public re-exports. + - Must preserve: delete-object field names and types, replication-state helper + semantics, ECStore object/delete operation associated types, scanner delete + selection behavior, RustFS object delete behavior, and old ECStore import + compatibility. + - Risk defense: this is a pure DTO ownership move; it does not change + deletion control flow, replication decisions, lifecycle expiry behavior, or + object metadata persistence. + - Verification: focused compile checks, storage-api tests, migration and layer + guards, formatting check, diff hygiene, risk scan, full pre-commit, and + required three-expert review passed. + +- [x] `API-037` Clean delete-object DTO consumers. + - Current branch: `overtrue/arch-delete-object-contracts`. + - Current slice: migrate ECStore internal delete-object DTO consumers from + old `crate::store_api` imports to `rustfs-storage-api` contracts while + keeping public ECStore type aliases for downstream compatibility. + - Acceptance: ECStore object, set, lifecycle, and replication internals use + storage-api delete DTO contracts directly; public old-path type aliases + remain available; migration rules reject reintroducing ECStore internal + old-path delete DTO consumers. + - Must preserve: object delete result shape, batch delete error alignment, + lifecycle replication scheduling, MRF delete replay, replication retry + decisions, and old ECStore public import compatibility. + - Risk defense: this is a consumer import cleanup over identical type + definitions; it does not change delete control flow, replication decisions, + lifecycle expiry behavior, or object metadata persistence. + - Verification: focused ECStore/RustFS/scanner compile checks, migration and + layer guards, formatting check, diff hygiene, risk scan, full pre-commit, + and required three-expert review passed. + +- [x] `API-038` Narrow remaining `store_api` compatibility re-export surfaces. + - Current branch: `overtrue/arch-delete-object-contracts`. + - Current slice: replace whole-module `rustfs_ecstore::store_api` + compatibility re-exports in RustFS storage, scanner, heal, Swift, + S3 Select, IAM, and notify boundaries with explicit contract type + re-exports, and add a migration rule rejecting broad `store_api` + compatibility re-exports. + - Acceptance: storage compatibility boundaries expose only the concrete + `store_api` contracts their consumers use; downstream local aliases keep + the same names; migration rules reject reintroducing broad `store_api` + passthroughs in production compatibility boundaries. + - Must preserve: object info/options reader aliases, storage/list/multipart + operation trait bindings, scanner/heal/Swift/S3 Select/IAM/notify behavior, + and all ECStore-owned concrete type ownership. + - Risk defense: this is compatibility import surface cleanup only; it does + not move definitions, alter storage/runtime control flow, change object + metadata conversion, or mutate reader behavior. + - Verification: focused multi-crate compile, migration guard, formatting + check, diff hygiene, risk scan, full pre-commit, and required three-expert + review passed. + ## Phase 8 Background Controller Tasks - [x] `BGC-001` Inventory background services. @@ -1263,14 +1325,53 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block | Expert | Status | Notes | |---|---|---| -| Quality/architecture | passed | Production compatibility boundaries no longer hide unused ECStore re-exports behind broad unused-import allows; target-specific test exceptions remain scoped. | -| Migration preservation | passed | ECStore remains the owner of backing types and behavior; this slice only changes compatibility-boundary hygiene and guard coverage. | -| Testing/verification | passed | Focused compile checks, fuzz manifest compile, guards, formatting, diff hygiene, risk scan, and full `make pre-commit` passed. | +| Quality/architecture | passed | Delete-object DTO ownership lives in storage-api; ECStore internal consumers use storage-api directly while public aliases remain, and store_api compatibility boundaries now expose explicit contract types only. | +| Migration preservation | passed | Field shape, helper semantics, delete behavior, replication scheduling, MRF delete replay, object info/options aliases, reader aliases, and old ECStore public import compatibility are preserved. | +| Testing/verification | passed | Multi-crate compile, guards, formatting, diff hygiene, risk scan, and full `make pre-commit` passed. | ## Verification Notes Passed before push: +- API-038 current slice: + - `cargo check --tests -p rustfs -p rustfs-scanner -p rustfs-heal -p rustfs-protocols -p rustfs-s3select-api -p rustfs-iam -p rustfs-notify`: + passed. + - `./scripts/check_architecture_migration_rules.sh`: passed. + - `./scripts/check_layer_dependencies.sh`: passed. + - `cargo fmt --all --check`: passed. + - `git diff --check`: passed. + - Rust risk scan: passed; no new unwrap/expect, numeric casts, string error + public APIs, boxed public errors, production println/eprintln, or relaxed + ordering introduced in changed Rust files. + - `make pre-commit`: passed. + +- API-037 current slice: + - `cargo check --tests -p rustfs-ecstore -p rustfs -p rustfs-scanner`: + passed. + - `./scripts/check_architecture_migration_rules.sh`: passed. + - `./scripts/check_layer_dependencies.sh`: passed. + - `cargo fmt --all --check`: passed. + - `git diff --check`: passed. + - Rust risk scan: passed; no new unwrap/expect, numeric casts, string error + public APIs, boxed public errors, production println/eprintln, or relaxed + ordering introduced in changed Rust files. + - `make pre-commit`: passed. + +- API-036 current slice: + - `cargo test -p rustfs-storage-api`: passed. + - `cargo check --tests -p rustfs-storage-api -p rustfs-ecstore -p rustfs-scanner -p rustfs`: + passed. + - `./scripts/check_architecture_migration_rules.sh`: passed. + - `./scripts/check_layer_dependencies.sh`: passed. + - `cargo fmt --all --check`: passed. + - `git diff --check`: passed. + - Rust risk scan: passed; no new unwrap/expect, numeric casts, string error + public APIs, boxed public errors, production println/eprintln, or relaxed + ordering introduced in changed Rust files. + - `make pre-commit`: passed. + +API-035 prior slice: + - `cargo check --tests -p rustfs-scanner -p rustfs-heal -p rustfs-iam`: passed. - `cargo check --tests -p rustfs-protocols --features swift`: passed. diff --git a/rustfs/src/storage/mod.rs b/rustfs/src/storage/mod.rs index ba16504b5..81ce690e3 100644 --- a/rustfs/src/storage/mod.rs +++ b/rustfs/src/storage/mod.rs @@ -28,11 +28,11 @@ pub(crate) mod storage_compat; pub mod timeout_wrapper; pub mod tonic_service; -pub(crate) type StorageDeletedObject = crate::storage::storage_compat::ecstore::store_api::DeletedObject; +pub(crate) type StorageDeletedObject = rustfs_storage_api::DeletedObject; pub(crate) type StorageGetObjectReader = crate::storage::storage_compat::ecstore::store_api::GetObjectReader; pub(crate) type StorageObjectInfo = crate::storage::storage_compat::ecstore::store_api::ObjectInfo; pub(crate) type StorageObjectOptions = crate::storage::storage_compat::ecstore::store_api::ObjectOptions; -pub(crate) type StorageObjectToDelete = crate::storage::storage_compat::ecstore::store_api::ObjectToDelete; +pub(crate) type StorageObjectToDelete = rustfs_storage_api::ObjectToDelete; pub(crate) type StoragePutObjReader = crate::storage::storage_compat::ecstore::store_api::PutObjReader; #[cfg(test)] diff --git a/rustfs/src/storage/storage_compat.rs b/rustfs/src/storage/storage_compat.rs index 916038fe8..01cdec5a1 100644 --- a/rustfs/src/storage/storage_compat.rs +++ b/rustfs/src/storage/storage_compat.rs @@ -15,9 +15,13 @@ pub(crate) mod ecstore { pub(crate) use rustfs_ecstore::{ admin_server_info, bucket, client, disk, error, get_global_lock_client, global, metrics_realtime, - resolve_object_store_handle, rio, rpc, set_disk, store, store_api, + resolve_object_store_handle, rio, rpc, set_disk, store, }; + pub(crate) mod store_api { + pub(crate) use rustfs_ecstore::store_api::{GetObjectReader, ObjectInfo, ObjectOptions, PutObjReader}; + } + #[cfg(test)] pub(crate) use rustfs_ecstore::config; } diff --git a/rustfs/src/table_catalog.rs b/rustfs/src/table_catalog.rs index 7d776a90a..341db7150 100644 --- a/rustfs/src/table_catalog.rs +++ b/rustfs/src/table_catalog.rs @@ -10481,9 +10481,7 @@ mod tests { .await .unwrap(); backend.seed_object(bucket, &new_metadata, b"{}".to_vec()).await; - backend - .fail_put_attempt(rustfs_ecstore::disk::RUSTFS_META_BUCKET, &commit_path, 2) - .await; + backend.fail_put_attempt(RUSTFS_META_BUCKET, &commit_path, 2).await; store .commit_table(TableCommitRequest { diff --git a/scripts/check_architecture_migration_rules.sh b/scripts/check_architecture_migration_rules.sh index 57fd3fb1d..e756de883 100755 --- a/scripts/check_architecture_migration_rules.sh +++ b/scripts/check_architecture_migration_rules.sh @@ -57,11 +57,14 @@ STORE_API_OBJECT_HELPER_REEXPORTS_FILE="${TMP_DIR}/store_api_object_helper_reexp STORE_API_RANGE_HELPER_REEXPORTS_FILE="${TMP_DIR}/store_api_range_helper_reexports.txt" STORE_API_LIST_HELPER_REEXPORTS_FILE="${TMP_DIR}/store_api_list_helper_reexports.txt" STORE_API_LIST_RESPONSE_REEXPORTS_FILE="${TMP_DIR}/store_api_list_response_reexports.txt" +STORE_API_DELETE_DTO_REEXPORTS_FILE="${TMP_DIR}/store_api_delete_dto_reexports.txt" +STORE_API_DELETE_DTO_INTERNAL_HITS_FILE="${TMP_DIR}/store_api_delete_dto_internal_hits.txt" STORE_API_EXTERNAL_LIST_CONSUMER_HITS_FILE="${TMP_DIR}/store_api_external_list_consumer_hits.txt" STORE_API_EXTERNAL_OPERATION_CONSUMER_HITS_FILE="${TMP_DIR}/store_api_external_operation_consumer_hits.txt" STORE_API_OBJECT_OPERATION_LOCAL_METHOD_HITS_FILE="${TMP_DIR}/store_api_object_operation_local_method_hits.txt" DIRECT_ECSTORE_IMPORT_HITS_FILE="${TMP_DIR}/direct_ecstore_import_hits.txt" PRODUCTION_UNUSED_COMPAT_ALLOW_HITS_FILE="${TMP_DIR}/production_unused_compat_allow_hits.txt" +BROAD_STORE_API_COMPAT_REEXPORT_HITS_FILE="${TMP_DIR}/broad_store_api_compat_reexport_hits.txt" awk ' /^## PR Types$/ { @@ -202,6 +205,10 @@ require_source_line \ "crates/storage-api/src/lib.rs" \ "pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};" \ "storage-api public object helper contract re-export" +require_source_line \ + "crates/storage-api/src/lib.rs" \ + "pub use object::{DeletedObject, ObjectToDelete};" \ + "storage-api public delete object contract re-export" require_source_line \ "crates/storage-api/src/lib.rs" \ "pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ListOperations, ObjectInfoOrErr};" \ @@ -302,6 +309,26 @@ if [[ -s "$STORE_API_LIST_RESPONSE_REEXPORTS_FILE" ]]; then report_failure "old ecstore store_api list response path reintroduced: $(paste -sd '; ' "$STORE_API_LIST_RESPONSE_REEXPORTS_FILE")" fi +( + cd "$ROOT_DIR" + rg -n --no-heading 'pub(?:\(crate\))? use rustfs_storage_api(?:::\{[^}]*\b(?:DeletedObject|ObjectToDelete)\b|::(?:DeletedObject|ObjectToDelete)\b)|pub struct (?:DeletedObject|ObjectToDelete)\b' \ + crates/ecstore/src/store_api.rs crates/ecstore/src/store_api/types.rs || true +) >"$STORE_API_DELETE_DTO_REEXPORTS_FILE" + +if [[ -s "$STORE_API_DELETE_DTO_REEXPORTS_FILE" ]]; then + report_failure "old ecstore store_api delete DTO path reintroduced: $(paste -sd '; ' "$STORE_API_DELETE_DTO_REEXPORTS_FILE")" +fi + +( + cd "$ROOT_DIR" + rg -n --no-heading 'crate::store_api::(?:DeletedObject|ObjectToDelete)|store_api::\{[^}]*\b(?:DeletedObject|ObjectToDelete)\b' \ + crates/ecstore/src --glob '*.rs' --glob '!store_api/types.rs' || true +) >"$STORE_API_DELETE_DTO_INTERNAL_HITS_FILE" + +if [[ -s "$STORE_API_DELETE_DTO_INTERNAL_HITS_FILE" ]]; then + report_failure "ecstore internal delete DTO old store_api path reintroduced: $(paste -sd '; ' "$STORE_API_DELETE_DTO_INTERNAL_HITS_FILE")" +fi + ( cd "$ROOT_DIR" rg -n --no-heading 'rustfs_ecstore::store_api(?:::\{[^}]*\b(?:ListObjectVersionsInfo|ListObjectsV2Info|ObjectInfoOrErr)\b|::(?:ListObjectVersionsInfo|ListObjectsV2Info|ObjectInfoOrErr)\b)' \ @@ -348,6 +375,19 @@ if [[ -s "$PRODUCTION_UNUSED_COMPAT_ALLOW_HITS_FILE" ]]; then report_failure "production storage compatibility boundaries must not hide unused ECStore re-exports: $(paste -sd '; ' "$PRODUCTION_UNUSED_COMPAT_ALLOW_HITS_FILE")" fi +( + cd "$ROOT_DIR" + rg -n --no-heading '(?:use|pub(?:\([^)]*\))? use) rustfs_ecstore::\{[^}]*\bstore_api\b[^}]*\}|(?:use|pub(?:\([^)]*\))? use) rustfs_ecstore::store_api\s*(?:;|as\b)' \ + rustfs/src crates/*/src fuzz/fuzz_targets \ + --glob '*storage_compat.rs' \ + --glob '!crates/ecstore/**' \ + --glob '!crates/e2e_test/**' || true +) >"$BROAD_STORE_API_COMPAT_REEXPORT_HITS_FILE" + +if [[ -s "$BROAD_STORE_API_COMPAT_REEXPORT_HITS_FILE" ]]; then + report_failure "storage compatibility boundaries must expose explicit store_api contracts only: $(paste -sd '; ' "$BROAD_STORE_API_COMPAT_REEXPORT_HITS_FILE")" +fi + ( cd "$ROOT_DIR" rg -n --no-heading 'async fn (get_object_reader|put_object|get_object_info|verify_object_integrity|copy_object|delete_object_version|delete_object|delete_objects|put_object_metadata|get_object_tags|put_object_tags|delete_object_tags|add_partial|transition_object|restore_transitioned_object|list_multipart_uploads|new_multipart_upload|copy_object_part|put_object_part|get_multipart_info|list_object_parts|abort_multipart_upload|complete_multipart_upload|heal_format|heal_bucket|heal_object|get_pool_and_set|check_abandoned_parts|new_ns_lock)\b' \