refactor: move heal and namespace contracts (#3560)

This commit is contained in:
安正超
2026-06-18 11:00:21 +08:00
committed by GitHub
parent e5cad7ed20
commit 57403525ee
21 changed files with 224 additions and 86 deletions
+1 -1
View File
@@ -21,11 +21,11 @@ use crate::bucket::utils::{deserialize, is_meta_bucketname};
use crate::error::{Error, Result, is_err_bucket_not_found};
use crate::global::{GLOBAL_Endpoints, is_dist_erasure, is_erasure, resolve_object_store_handle};
use crate::store::ECStore;
use crate::store_api::HealOperations as _;
use futures::future::join_all;
use lazy_static::lazy_static;
use rustfs_common::heal_channel::HealOpts;
use rustfs_policy::policy::BucketPolicy;
use rustfs_storage_api::HealOperations as _;
use s3s::dto::ReplicationConfiguration;
use s3s::dto::{
AccelerateConfiguration, BucketLifecycleConfiguration, BucketLoggingStatus, CORSConfiguration, NotificationConfiguration,
+6 -2
View File
@@ -1217,7 +1217,7 @@ mod tests {
use crate::error::{Error, Result};
use crate::global::{is_dist_erasure, is_erasure, is_erasure_sd, update_erasure_type};
use crate::set_disk::SetDisks;
use crate::store_api::{GetObjectReader, NamespaceLocking, ObjectInfo, ObjectOptions, PutObjReader};
use crate::store_api::{GetObjectReader, ObjectInfo, ObjectOptions, PutObjReader};
use http::HeaderMap;
use rustfs_config::audit::{AUDIT_AMQP_SUB_SYS, AUDIT_KAFKA_SUB_SYS, AUDIT_MQTT_SUB_SYS, AUDIT_WEBHOOK_SUB_SYS};
use rustfs_config::notify::{
@@ -1231,6 +1231,7 @@ mod tests {
use rustfs_lock::client::LockClient;
use rustfs_lock::client::local::LocalClient;
use rustfs_lock::{LockError, LockInfo, LockResponse, LockStats};
use rustfs_storage_api::NamespaceLocking as _;
use rustfs_storage_api::{HTTPRangeSpec, StorageAdminApi};
use serde_json::Value;
use serial_test::serial;
@@ -1459,7 +1460,10 @@ mod tests {
}
#[async_trait::async_trait]
impl NamespaceLocking for LockingConfigStorage {
impl rustfs_storage_api::NamespaceLocking for LockingConfigStorage {
type Error = crate::error::Error;
type NamespaceLock = rustfs_lock::NamespaceLockWrapper;
async fn new_ns_lock(&self, bucket: &str, object: &str) -> Result<rustfs_lock::NamespaceLockWrapper> {
self.set_disks.new_ns_lock(bucket, object).await
}
+3 -2
View File
@@ -38,7 +38,7 @@ use crate::error::{
use crate::notification_sys::get_global_notification_sys;
use crate::resolve_object_store_handle;
use crate::set_disk::SetDisks;
use crate::store_api::{GetObjectReader, HealOperations, ObjectOptions};
use crate::store_api::{GetObjectReader, ObjectOptions};
use crate::{global::GLOBAL_LifecycleSys, sets::Sets, store::ECStore};
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
use futures::{StreamExt, future::BoxFuture, stream::FuturesUnordered};
@@ -51,7 +51,8 @@ use rustfs_common::heal_channel::HealOpts;
use rustfs_concurrency::workers::Workers;
use rustfs_filemeta::{FileInfoVersions, MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams};
use rustfs_storage_api::{
BucketOperations, BucketOptions, MakeBucketOptions, ObjectIO as _, ObjectOperations as _, StorageAdminApi,
BucketOperations, BucketOptions, HealOperations as _, MakeBucketOptions, ObjectIO as _, ObjectOperations as _,
StorageAdminApi,
};
use rustfs_utils::path::{encode_dir_object, path_join, path_to_bucket_object, path_to_bucket_object_with_base_path};
use s3s::dto::{BucketLifecycleConfiguration, DefaultRetention, ReplicationConfiguration};
+13 -6
View File
@@ -52,8 +52,8 @@ use crate::{
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, HealOperations, ListObjectsV2Info, MultipartOperations, NamespaceLocking, ObjectIO,
ObjectInfo, ObjectOperations, PutObjReader,
DeletedObject, GetObjectReader, ListObjectsV2Info, MultipartOperations, ObjectIO, ObjectInfo, ObjectOperations,
PutObjReader,
},
store_init::load_format_erasure,
};
@@ -90,7 +90,7 @@ use rustfs_storage_api::{
BucketInfo, BucketOperations, BucketOptions, CompletePart, DeleteBucketOptions, ListMultipartsInfo, ListPartsInfo,
MakeBucketOptions, MultipartInfo, MultipartUploadResult, PartInfo,
};
use rustfs_storage_api::{MultipartOperations as _, ObjectIO as _, ObjectOperations as _};
use rustfs_storage_api::{MultipartOperations as _, NamespaceLocking as _, ObjectIO as _, ObjectOperations as _};
use rustfs_utils::http::headers::AMZ_OBJECT_TAGGING;
use rustfs_utils::http::headers::AMZ_STORAGE_CLASS;
use rustfs_utils::http::headers::{
@@ -1729,7 +1729,10 @@ impl SetDisks {
}
#[async_trait::async_trait]
impl NamespaceLocking for SetDisks {
impl rustfs_storage_api::NamespaceLocking for SetDisks {
type Error = Error;
type NamespaceLock = NamespaceLockWrapper;
#[tracing::instrument(skip(self))]
async fn new_ns_lock(&self, bucket: &str, object: &str) -> Result<NamespaceLockWrapper> {
let set_lock = if is_dist_erasure().await {
@@ -4144,7 +4147,11 @@ impl rustfs_storage_api::MultipartOperations for SetDisks {
}
#[async_trait::async_trait]
impl HealOperations for SetDisks {
impl rustfs_storage_api::HealOperations for SetDisks {
type Error = Error;
type HealResultItem = HealResultItem;
type HealOptions = HealOpts;
#[tracing::instrument(skip(self))]
async fn heal_format(&self, _dry_run: bool) -> Result<(HealResultItem, Option<Error>)> {
unimplemented!()
@@ -5019,7 +5026,7 @@ mod tests {
use rustfs_filemeta::ReplicationState;
use rustfs_lock::client::local::LocalClient;
use rustfs_lock::{LockError, LockInfo, LockResponse, LockStats};
use rustfs_storage_api::{CompletePart, ObjectOperations as _};
use rustfs_storage_api::{CompletePart, NamespaceLocking as _, ObjectOperations as _};
use serial_test::serial;
use std::collections::HashMap;
use tempfile::TempDir;
+1
View File
@@ -14,6 +14,7 @@
use super::*;
use rustfs_config::{DEFAULT_OBJECT_ZERO_COPY_ENABLE, ENV_OBJECT_ZERO_COPY_ENABLE};
use rustfs_storage_api::NamespaceLocking as _;
impl SetDisks {
#[tracing::instrument(skip(self, opts), fields(bucket = %bucket, object = %object, version_id = %version_id))]
+11 -4
View File
@@ -28,8 +28,8 @@ use crate::{
global::{GLOBAL_LOCAL_DISK_SET_DRIVES, get_global_lock_clients, is_dist_erasure},
set_disk::SetDisks,
store_api::{
DeletedObject, GetObjectReader, HealOperations, ListObjectVersionsInfo, ListObjectsV2Info, NamespaceLocking, ObjectInfo,
ObjectOptions, ObjectToDelete, PutObjReader,
DeletedObject, GetObjectReader, ListObjectVersionsInfo, ListObjectsV2Info, ObjectInfo, ObjectOptions, ObjectToDelete,
PutObjReader,
},
store_init::{check_format_erasure_values, get_format_erasure_in_quorum, load_format_erasure_all, save_format_file},
};
@@ -826,7 +826,11 @@ impl rustfs_storage_api::MultipartOperations for Sets {
}
#[async_trait::async_trait]
impl HealOperations for Sets {
impl rustfs_storage_api::HealOperations for Sets {
type Error = Error;
type HealResultItem = HealResultItem;
type HealOptions = HealOpts;
#[tracing::instrument(skip(self))]
async fn heal_format(&self, dry_run: bool) -> Result<(HealResultItem, Option<Error>)> {
let (disks, _) = init_storage_disks_with_errors(
@@ -938,7 +942,10 @@ impl HealOperations for Sets {
}
#[async_trait::async_trait]
impl NamespaceLocking for Sets {
impl rustfs_storage_api::NamespaceLocking for Sets {
type Error = Error;
type NamespaceLock = NamespaceLockWrapper;
async fn new_ns_lock(&self, bucket: &str, object: &str) -> Result<NamespaceLockWrapper> {
self.disk_set[0].new_ns_lock(bucket, object).await
}
+10 -6
View File
@@ -62,10 +62,7 @@ use crate::{
endpoints::EndpointServerPools,
rpc::S3PeerSys,
sets::Sets,
store_api::{
DeletedObject, GetObjectReader, HealOperations, ListObjectsV2Info, NamespaceLocking, ObjectInfo, ObjectOptions,
ObjectToDelete, PutObjReader,
},
store_api::{DeletedObject, GetObjectReader, ListObjectsV2Info, ObjectInfo, ObjectOptions, ObjectToDelete, PutObjReader},
store_init,
};
use futures::future::join_all;
@@ -708,7 +705,11 @@ impl rustfs_storage_api::MultipartOperations for ECStore {
}
#[async_trait::async_trait]
impl HealOperations for ECStore {
impl rustfs_storage_api::HealOperations for ECStore {
type Error = Error;
type HealResultItem = HealResultItem;
type HealOptions = HealOpts;
#[instrument(skip(self))]
async fn heal_format(&self, dry_run: bool) -> Result<(HealResultItem, Option<Error>)> {
self.handle_heal_format(dry_run).await
@@ -741,7 +742,10 @@ impl HealOperations for ECStore {
}
#[async_trait::async_trait]
impl NamespaceLocking for ECStore {
impl rustfs_storage_api::NamespaceLocking for ECStore {
type Error = Error;
type NamespaceLock = NamespaceLockWrapper;
async fn new_ns_lock(&self, bucket: &str, object: &str) -> Result<NamespaceLockWrapper> {
self.handle_new_ns_lock(bucket, object).await
}
+1
View File
@@ -19,6 +19,7 @@ use crate::bucket::{
};
use crate::global::get_global_bucket_monitor;
use crate::set_disk::get_lock_acquire_timeout;
use rustfs_storage_api::NamespaceLocking as _;
fn should_override_created_from_metadata(created: OffsetDateTime) -> bool {
created != OffsetDateTime::UNIX_EPOCH
+1
View File
@@ -13,6 +13,7 @@
// limitations under the License.
use super::*;
use rustfs_storage_api::HealOperations as _;
const LOG_COMPONENT_ECSTORE: &str = "ecstore";
const LOG_SUBSYSTEM_HEAL: &str = "heal";
+1 -1
View File
@@ -14,7 +14,7 @@
use super::*;
use crate::config::get_global_storage_class;
use rustfs_storage_api::{ObjectOperations as _, StorageAdminApi};
use rustfs_storage_api::{NamespaceLocking as _, ObjectOperations as _, StorageAdminApi};
struct LatestObjectInfoCandidate {
info: Option<ObjectInfo>,
+19 -16
View File
@@ -131,24 +131,27 @@ impl<T> MultipartOperations for T where
}
/// Healing and repair operations.
#[async_trait::async_trait]
pub trait HealOperations: Send + Sync + Debug {
async fn heal_format(&self, dry_run: bool) -> Result<(HealResultItem, Option<Error>)>;
async fn heal_bucket(&self, bucket: &str, opts: &HealOpts) -> Result<HealResultItem>;
async fn heal_object(
&self,
bucket: &str,
object: &str,
version_id: &str,
opts: &HealOpts,
) -> Result<(HealResultItem, Option<Error>)>;
async fn get_pool_and_set(&self, id: &str) -> Result<(Option<usize>, Option<usize>, Option<usize>)>;
async fn check_abandoned_parts(&self, bucket: &str, object: &str, opts: &HealOpts) -> Result<()>;
pub trait HealOperations:
rustfs_storage_api::HealOperations<Error = Error, HealResultItem = HealResultItem, HealOptions = HealOpts> + Send + Sync + Debug
{
}
impl<T> HealOperations for T where
T: rustfs_storage_api::HealOperations<Error = Error, HealResultItem = HealResultItem, HealOptions = HealOpts>
+ Send
+ Sync
+ Debug
{
}
/// Namespace lock operations needed by consumers that only coordinate object
/// mutations but do not require the full storage API surface.
#[async_trait::async_trait]
pub trait NamespaceLocking: Send + Sync + Debug + 'static {
async fn new_ns_lock(&self, bucket: &str, object: &str) -> Result<NamespaceLockWrapper>;
pub trait NamespaceLocking:
rustfs_storage_api::NamespaceLocking<Error = Error, NamespaceLock = NamespaceLockWrapper> + Send + Sync + Debug + 'static
{
}
impl<T> NamespaceLocking for T where
T: rustfs_storage_api::NamespaceLocking<Error = Error, NamespaceLock = NamespaceLockWrapper> + Send + Sync + Debug + 'static
{
}
@@ -12,8 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use rustfs_ecstore::{disk::DiskStore, error::Error, store::ECStore, store_api::NamespaceLocking};
use rustfs_storage_api::StorageAdminApi;
use rustfs_common::heal_channel::HealOpts;
use rustfs_ecstore::{
disk::DiskStore,
error::Error,
store::ECStore,
store_api::{HealOperations, NamespaceLocking},
};
use rustfs_lock::NamespaceLockWrapper;
use rustfs_madmin::heal_commands::HealResultItem;
use rustfs_storage_api::{HealOperations as StorageHealOperations, NamespaceLocking as StorageNamespaceLocking, StorageAdminApi};
fn storage_admin_api_type_name<T>() -> &'static str
where
@@ -34,6 +42,27 @@ where
std::any::type_name::<T>()
}
fn heal_operations_type_name<T>() -> &'static str
where
T: HealOperations,
{
std::any::type_name::<T>()
}
fn storage_namespace_locking_type_name<T>() -> &'static str
where
T: StorageNamespaceLocking<Error = Error, NamespaceLock = NamespaceLockWrapper>,
{
std::any::type_name::<T>()
}
fn storage_heal_operations_type_name<T>() -> &'static str
where
T: StorageHealOperations<Error = Error, HealResultItem = HealResultItem, HealOptions = HealOpts>,
{
std::any::type_name::<T>()
}
#[test]
fn ecstore_implements_storage_admin_api_contract() {
assert!(storage_admin_api_type_name::<ECStore>().ends_with("::ECStore"));
@@ -43,3 +72,18 @@ fn ecstore_implements_storage_admin_api_contract() {
fn ecstore_implements_namespace_locking_contract() {
assert!(namespace_locking_type_name::<ECStore>().ends_with("::ECStore"));
}
#[test]
fn ecstore_implements_heal_operations_contract() {
assert!(heal_operations_type_name::<ECStore>().ends_with("::ECStore"));
}
#[test]
fn ecstore_implements_storage_namespace_locking_contract() {
assert!(storage_namespace_locking_type_name::<ECStore>().ends_with("::ECStore"));
}
#[test]
fn ecstore_implements_storage_heal_operations_contract() {
assert!(storage_heal_operations_type_name::<ECStore>().ends_with("::ECStore"));
}
+3 -2
View File
@@ -19,11 +19,12 @@ use rustfs_ecstore::{
disk::{DiskStore, endpoint::Endpoint},
error::StorageError,
store::ECStore,
store_api::{HealOperations, ObjectOptions},
store_api::ObjectOptions,
};
use rustfs_madmin::heal_commands::HealResultItem;
use rustfs_storage_api::{
BucketInfo, BucketOperations, DiskSetSelector, ListOperations as _, ObjectIO as _, ObjectOperations as _, StorageAdminApi,
BucketInfo, BucketOperations, DiskSetSelector, HealOperations as _, ListOperations as _, ObjectIO as _,
ObjectOperations as _, StorageAdminApi,
};
use std::sync::Arc;
use tracing::{debug, error, warn};
+2 -2
View File
@@ -45,8 +45,8 @@ use rustfs_ecstore::disk::RUSTFS_META_BUCKET;
use rustfs_ecstore::error::Error as EcstoreError;
use rustfs_ecstore::global::is_erasure_sd;
use rustfs_ecstore::store::ECStore;
use rustfs_ecstore::store_api::{NamespaceLocking as _, ObjectIO};
use rustfs_storage_api::{BucketOperations, BucketOptions};
use rustfs_ecstore::store_api::ObjectIO;
use rustfs_storage_api::{BucketOperations, BucketOptions, NamespaceLocking as _};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
use tokio::time::{Duration, Instant};
+1 -1
View File
@@ -25,7 +25,7 @@ pub use bucket::{BucketInfo, BucketOperations, BucketOptions, DeleteBucketOption
pub use error::{StorageErrorCode, StorageResult};
pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo};
pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};
pub use object::{HealOperations, MultipartOperations, NamespaceLocking, ObjectIO, ObjectOperations};
pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ListOperations, ObjectInfoOrErr};
pub use object::{MultipartOperations, ObjectIO, ObjectOperations};
pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState};
pub use object::{VersionMarker, WalkOptions, WalkVersionsSortOrder};
+27
View File
@@ -408,6 +408,33 @@ pub trait MultipartOperations: Send + Sync + fmt::Debug {
) -> Result<Self::ObjectInfo, Self::Error>;
}
#[async_trait::async_trait]
pub trait HealOperations: Send + Sync + fmt::Debug {
type Error: std::error::Error + Send + Sync + 'static;
type HealResultItem: Send + 'static;
type HealOptions: Send + Sync + 'static;
async fn heal_format(&self, dry_run: bool) -> Result<(Self::HealResultItem, Option<Self::Error>), Self::Error>;
async fn heal_bucket(&self, bucket: &str, opts: &Self::HealOptions) -> Result<Self::HealResultItem, Self::Error>;
async fn heal_object(
&self,
bucket: &str,
object: &str,
version_id: &str,
opts: &Self::HealOptions,
) -> Result<(Self::HealResultItem, Option<Self::Error>), Self::Error>;
async fn get_pool_and_set(&self, id: &str) -> Result<(Option<usize>, Option<usize>, Option<usize>), Self::Error>;
async fn check_abandoned_parts(&self, bucket: &str, object: &str, opts: &Self::HealOptions) -> Result<(), Self::Error>;
}
#[async_trait::async_trait]
pub trait NamespaceLocking: Send + Sync + fmt::Debug + 'static {
type Error: std::error::Error + Send + Sync + 'static;
type NamespaceLock: Send + 'static;
async fn new_ns_lock(&self, bucket: &str, object: &str) -> Result<Self::NamespaceLock, Self::Error>;
}
#[derive(Debug, Default)]
pub struct ListObjectsInfo<ObjectItem> {
pub is_truncated: bool,
+3 -2
View File
@@ -95,12 +95,13 @@ Required `rustfs-storage-api` public re-exports:
- `pub use error::{StorageErrorCode, StorageResult};`
- `pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo};`
- `pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};`
- `pub use object::{HealOperations, MultipartOperations, NamespaceLocking, ObjectIO, ObjectOperations};`
- `pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ListOperations, ObjectInfoOrErr};`
- `pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState};`
- `pub use object::{VersionMarker, WalkOptions, WalkVersionsSortOrder};`
ECStore must keep compile-time coverage for both `StorageAdminApi` and the
separate `NamespaceLocking` operation group.
ECStore must keep compile-time coverage for `StorageAdminApi`, `HealOperations`,
and the separate `NamespaceLocking` operation group.
The old `StorageAPI` aggregate facade must not reappear in production
`crates/ecstore/src` or `rustfs/src` code after the storage operation groups
+51 -31
View File
@@ -5,19 +5,19 @@ 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-object-operation-contracts`
- Baseline: `main` at `3fb4cb3d65a2e037fc2e5ede32bf81c1f15c9fb7`
after the list operations contract merge.
- Branch: `overtrue/arch-storage-operation-consumer-cleanup`
- Baseline: `main` at `e5cad7ed207265f5312c58d8dac6042e31ba95a3`
after the object and multipart operation contract merge.
- PR type for this branch: `api-extraction`
- Runtime behavior changes: no external behavior change expected.
- Rust code changes: move `ObjectIO`, `ObjectOperations`, and
`MultipartOperations` into `rustfs-storage-api` as generic operation
contracts with associated ECStore-bound types, then keep ECStore's existing
public names as fixed associated-type compatibility subtraits.
- CI/script changes: extend migration guards for the object/multipart operation
public re-exports and ECStore local-method regressions.
- Docs changes: record the object and multipart operation contract extraction
slice.
- Rust code changes: move `HealOperations` and `NamespaceLocking` into
`rustfs-storage-api` as generic operation contracts with associated
ECStore-bound types, then keep ECStore's existing public names as fixed
associated-type compatibility subtraits.
- CI/script changes: extend migration guards for the heal/namespace-lock
operation public re-exports and ECStore local-method regressions.
- Docs changes: record the heal and namespace-lock operation contract
extraction slice.
## Phase 0 Tasks
@@ -709,6 +709,28 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
- Verification: focused storage-api tests, ECStore/RustFS/downstream compile
checks, migration/layer guards, formatting, diff hygiene, Rust risk scan,
full pre-commit, and required three-expert review passed.
- [x] `API-023` Move heal and namespace-lock operation contracts.
- Completed slice: move `HealOperations` and `NamespaceLocking` from ECStore
`store_api/traits.rs` into `rustfs-storage-api` as generic public
operation contracts over ECStore heal result/options, namespace-lock
wrapper, and error associated types; keep ECStore's old public trait names
as fixed associated-type compatibility subtraits.
- Acceptance: `rustfs-storage-api` exports the heal and namespace-lock
operation contracts, ECStore no longer defines local heal/namespace-lock
method signatures, focused consumers use the shared trait for method
resolution, and migration guards reject dropping the public storage-api
re-export or reintroducing local ECStore method definitions.
- Must preserve: heal format/bucket/object behavior, abandoned-part checks,
pool/set lookup behavior, namespace-lock acquisition behavior, ECStore
public compatibility bounds, and all runtime lock/heal implementation
bodies.
- Risk defense: only the trait contracts cross into `rustfs-storage-api`;
ECStore keeps concrete associated type bindings, `HealOpts`,
`HealResultItem`, `NamespaceLockWrapper`, lock implementation, peer heal
behavior, set/pool dispatch, and storage error mapping.
- Verification: focused storage-api/ECStore/RustFS/heal/scanner compile
checks, migration/layer guards, formatting, diff hygiene, Rust risk scan,
full pre-commit, and required three-expert review passed.
## Phase 8 Background Controller Tasks
@@ -986,17 +1008,17 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
| Expert | Status | Notes |
|---|---|---|
| Quality/architecture | passed | Generic object I/O, object operation, and multipart operation traits now live in `rustfs-storage-api`; ECStore still owns concrete type bindings and implementations. |
| Migration preservation | passed | Object read/write, metadata/tag/delete, multipart, and existing ECStore generic bound import paths are preserved through compatibility subtraits. |
| Testing/verification | passed | Focused storage-api tests, downstream compile checks, migration/layer guards, formatting, diff hygiene, Rust risk scan, and full `make pre-commit` passed. |
| Quality/architecture | passed | Generic heal and namespace-lock traits now live in `rustfs-storage-api`; ECStore still owns concrete type bindings and implementations. |
| Migration preservation | passed | Existing ECStore public trait names remain as fixed associated-type compatibility subtraits while method resolution moves to shared traits where needed. |
| Testing/verification | passed | Focused storage-api tests, ECStore compat tests, downstream compile checks, migration/layer guards, formatting, diff hygiene, Rust risk scan, and full `make pre-commit` passed. |
## Verification Notes
Passed before push:
- `cargo test -p rustfs-storage-api`: passed.
- `cargo check --tests -p rustfs-storage-api -p rustfs-ecstore -p rustfs -p rustfs-scanner -p rustfs-protocols`: passed.
- `cargo check --tests -p rustfs-heal -p rustfs-protocols`: passed.
- `cargo test -p rustfs-ecstore --test ecstore_contract_compat_test`: passed.
- `cargo check --tests -p rustfs-storage-api -p rustfs-ecstore -p rustfs -p rustfs-heal -p rustfs-scanner`: passed.
- `./scripts/check_architecture_migration_rules.sh`: passed.
- `./scripts/check_layer_dependencies.sh`: passed.
- `cargo fmt --all --check`: passed.
@@ -1004,28 +1026,26 @@ Passed before push:
- Rust risk scan: no new `unwrap`/`expect`, panic/todo markers, `unsafe`,
process-spawning calls, lossy casts, println/eprintln, or relaxed ordering in
added Rust lines.
- `make pre-commit`: passed; nextest reported 6204 tests passed and 111
- `make pre-commit`: passed; nextest reported 6207 tests passed and 111
skipped, and doctests passed.
Notes:
- This slice follows the list operations contract branch and keeps the old
- This slice follows the object and multipart operation contract branch and keeps the old
aggregate facade, bucket DTO, multipart DTO, bucket operation contract, object
helper, range helper, list helper, object precondition, list response, and
walk/list operation contract guards active.
- The shared object and multipart operation contracts are now owned by
walk/list/object/multipart operation contract guards active.
- The shared heal and namespace-lock operation contracts are now owned by
`rustfs-storage-api`; ECStore keeps the concrete associated type bindings,
readers, `ObjectInfo`, `ObjectOptions`, `PutObjReader`, filemeta adaptation,
storage error mapping, lifecycle/replication, rio, compression/encryption,
and implementation behavior.
- The slice does not alter object, list, walk, multipart, bucket, delete,
namespace-lock, reader, or tag/metadata runtime behavior.
`HealOpts`, `HealResultItem`, `NamespaceLockWrapper`, lock implementation,
peer heal behavior, set/pool dispatch, and storage error mapping.
- The slice does not alter heal, namespace-lock, object, list, walk,
multipart, bucket, delete, reader, or tag/metadata runtime behavior.
## Handoff Notes
- Object and multipart operation contract cleanup is stacked on the list
operations contract
branch.
- After this lands, remaining storage work can continue by extracting larger
low-coupling object metadata/option/reader slices or by narrowing remaining
operation-group consumers.
- Heal and namespace-lock operation contract cleanup is based on the merged
object and multipart operation contract branch.
- After this lands, remaining storage work can continue by removing low-value
ECStore compatibility imports or extracting larger low-coupling object
metadata/option/reader slices.
+1 -1
View File
@@ -25,10 +25,10 @@ use matchit::Params;
use rustfs_common::heal_channel::{HealChannelPriority, HealChannelRequest, HealOpts, HealRequestSource};
use rustfs_config::MAX_HEAL_REQUEST_SIZE;
use rustfs_ecstore::bucket::utils::is_valid_object_prefix;
use rustfs_ecstore::store_api::HealOperations;
use rustfs_ecstore::store_utils::is_reserved_or_invalid_bucket;
use rustfs_policy::policy::action::{Action, AdminAction};
use rustfs_scanner::scanner::{BackgroundHealInfo, read_background_heal_info};
use rustfs_storage_api::HealOperations as _;
use rustfs_utils::path::path_join;
use s3s::header::{CONTENT_LENGTH, CONTENT_TYPE};
use s3s::{Body, S3Request, S3Response, S3Result, s3_error};
+2 -2
View File
@@ -18,10 +18,10 @@ use rustfs_ecstore::{
disk::endpoint::Endpoint,
endpoints::{EndpointServerPools, Endpoints, PoolEndpoints},
store::ECStore,
store_api::{HealOperations, ObjectOptions, PutObjReader},
store_api::{ObjectOptions, PutObjReader},
};
use rustfs_object_capacity::capacity_manager::{HybridStrategyConfig, create_isolated_manager};
use rustfs_storage_api::{BucketOperations, BucketOptions, MakeBucketOptions, ObjectIO as _};
use rustfs_storage_api::{BucketOperations, BucketOptions, HealOperations as _, MakeBucketOptions, ObjectIO as _};
use serial_test::serial;
use std::{
collections::HashSet,
+21 -5
View File
@@ -204,7 +204,7 @@ require_source_line \
"storage-api public list response contract re-export"
require_source_line \
"crates/storage-api/src/lib.rs" \
"pub use object::{MultipartOperations, ObjectIO, ObjectOperations};" \
"pub use object::{HealOperations, MultipartOperations, NamespaceLocking, ObjectIO, ObjectOperations};" \
"storage-api public object operation contract re-export"
require_source_line \
"crates/storage-api/src/lib.rs" \
@@ -300,12 +300,12 @@ 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)\b' \
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' \
crates/ecstore/src/store_api/traits.rs || true
) >"$STORE_API_OBJECT_OPERATION_LOCAL_METHOD_HITS_FILE"
if [[ -s "$STORE_API_OBJECT_OPERATION_LOCAL_METHOD_HITS_FILE" ]]; then
report_failure "old ecstore object/multipart operation method signatures reintroduced: $(paste -sd '; ' "$STORE_API_OBJECT_OPERATION_LOCAL_METHOD_HITS_FILE")"
report_failure "old ecstore operation method signatures reintroduced: $(paste -sd '; ' "$STORE_API_OBJECT_OPERATION_LOCAL_METHOD_HITS_FILE")"
fi
require_source_contains \
@@ -322,8 +322,12 @@ require_source_contains \
"ECStore MultipartOperations compatibility binding"
require_source_contains \
"crates/ecstore/src/store_api/traits.rs" \
"pub trait NamespaceLocking: Send + Sync + Debug + 'static" \
"separate namespace-locking operation-group trait"
"rustfs_storage_api::HealOperations<" \
"ECStore HealOperations compatibility binding"
require_source_contains \
"crates/ecstore/src/store_api/traits.rs" \
"rustfs_storage_api::NamespaceLocking<" \
"ECStore NamespaceLocking compatibility binding"
require_source_contains \
"crates/ecstore/tests/ecstore_contract_compat_test.rs" \
"fn ecstore_implements_storage_admin_api_contract()" \
@@ -332,6 +336,18 @@ require_source_contains \
"crates/ecstore/tests/ecstore_contract_compat_test.rs" \
"fn ecstore_implements_namespace_locking_contract()" \
"ECStore NamespaceLocking compile-time coverage test"
require_source_contains \
"crates/ecstore/tests/ecstore_contract_compat_test.rs" \
"fn ecstore_implements_heal_operations_contract()" \
"ECStore HealOperations compile-time coverage test"
require_source_contains \
"crates/ecstore/tests/ecstore_contract_compat_test.rs" \
"fn ecstore_implements_storage_namespace_locking_contract()" \
"ECStore storage-api NamespaceLocking compile-time coverage test"
require_source_contains \
"crates/ecstore/tests/ecstore_contract_compat_test.rs" \
"fn ecstore_implements_storage_heal_operations_contract()" \
"ECStore storage-api HealOperations compile-time coverage test"
if (( FAILURES > 0 )); then
exit 1