refactor(replication): isolate storage api contracts (#4240)

* refactor(replication): isolate storage api contracts

* docs(replication): record storage api contract boundary
This commit is contained in:
Zhengchao An
2026-07-03 22:10:16 +08:00
committed by GitHub
parent 6a81445a96
commit 6839e57c96
8 changed files with 54 additions and 4 deletions
@@ -37,6 +37,7 @@ paths.
| `ReplicationResyncContracts` | Resync options, target status, bucket status, status classifiers, and persisted resync/MRF status wire format. | Owned by `crates/replication`; ECStore imports them through `replication_resync_boundary.rs`, which maps crate errors to ECStore errors. | | `ReplicationResyncContracts` | Resync options, target status, bucket status, status classifiers, and persisted resync/MRF status wire format. | Owned by `crates/replication`; ECStore imports them through `replication_resync_boundary.rs`, which maps crate errors to ECStore errors. |
| `ReplicationConfigStore` | Replication config persistence and config-derived labels used by target options. | Config read/save helpers and storage class labels are exposed through the contract type in `replication_config_store.rs`. | | `ReplicationConfigStore` | Replication config persistence and config-derived labels used by target options. | Config read/save helpers and storage class labels are exposed through the contract type in `replication_config_store.rs`. |
| `ReplicationFileMeta` | Replication status, decisions, MRF entries, resync decisions, and target reset helpers. | `rustfs_filemeta` replication contracts are concentrated in `replication_filemeta_boundary.rs`; `FileInfo` remains in the storage boundary for storage trait bindings and walk options. | | `ReplicationFileMeta` | Replication status, decisions, MRF entries, resync decisions, and target reset helpers. | `rustfs_filemeta` replication contracts are concentrated in `replication_filemeta_boundary.rs`; `FileInfo` remains in the storage boundary for storage trait bindings and walk options. |
| `StorageApiReplicationContracts` | Storage-api delete DTO replication state/status helpers. | The temporary `rustfs-filemeta` dependency is isolated in `crates/storage-api/src/replication.rs` until the wire contracts can move without creating a `rustfs-replication` / `rustfs-storage-api` cycle. |
| `ReplicationObjectDecisionContracts` | Object replication options, delete replication decisions, resync target projection, multipart planning, and delete-marker retry classifiers. | Owned by `crates/replication`; ECStore imports them through `replication_object_decision_boundary.rs`. | | `ReplicationObjectDecisionContracts` | Object replication options, delete replication decisions, resync target projection, multipart planning, and delete-marker retry classifiers. | Owned by `crates/replication`; ECStore imports them through `replication_object_decision_boundary.rs`. |
| `ReplicationQueueContracts` | Queue admission, heal queue results/actions, worker operations, worker sizing, and backpressure decisions. | Owned by `crates/replication`; ECStore imports them through `replication_queue_boundary.rs`. | | `ReplicationQueueContracts` | Queue admission, heal queue results/actions, worker operations, worker sizing, and backpressure decisions. | Owned by `crates/replication`; ECStore imports them through `replication_queue_boundary.rs`. |
| `ReplicationStatsContracts` | Bucket stats, replication target stats, queue/proxy metrics, and worker metric snapshots. | Owned by `crates/replication`; ECStore imports them through `replication_stats_boundary.rs`. | | `ReplicationStatsContracts` | Bucket stats, replication target stats, queue/proxy metrics, and worker metric snapshots. | Owned by `crates/replication`; ECStore imports them through `replication_stats_boundary.rs`. |
@@ -82,6 +83,10 @@ paths.
compatibility facade. compatibility facade.
10. Keep ECStore owner modules outside `bucket/replication` behind bridge 10. Keep ECStore owner modules outside `bucket/replication` behind bridge
contracts when they need replication codec or config helper behavior. contracts when they need replication codec or config helper behavior.
11. Keep storage-api replication status/state helpers behind
`crates/storage-api/src/replication.rs` until the underlying wire contracts
can move without a `rustfs-replication` / `rustfs-storage-api` dependency
cycle.
## First Code-Bearing Step ## First Code-Bearing Step
+2 -3
View File
@@ -29,9 +29,8 @@ doctest = false
[dependencies] [dependencies]
async-trait.workspace = true async-trait.workspace = true
# NOTE: This dependency on rustfs-filemeta is a known architectural limitation. # Storage-facing replication contracts are isolated in src/replication.rs until
# The replication types (ReplicationStatusType, VersionPurgeStatusType, ReplicationState) # the underlying wire types can move without creating a replication/storage-api cycle.
# are shared between storage-api and filemeta. Moving them would require changes in 300+ files.
rustfs-filemeta.workspace = true rustfs-filemeta.workspace = true
serde.workspace = true serde.workspace = true
time.workspace = true time.workspace = true
+2
View File
@@ -23,6 +23,8 @@ pub mod object;
pub mod observability; pub mod observability;
pub mod topology; pub mod topology;
mod replication;
pub use admin::{DiskSetSelector, StorageAdminApi}; pub use admin::{DiskSetSelector, StorageAdminApi};
pub use bucket::{BucketInfo, BucketOperations, BucketOptions, DeleteBucketOptions, MakeBucketOptions, SRBucketDeleteOp}; pub use bucket::{BucketInfo, BucketOperations, BucketOptions, DeleteBucketOptions, MakeBucketOptions, SRBucketDeleteOp};
pub use capability::{CapabilitySnapshotError, CapabilityState, CapabilityStatus}; pub use capability::{CapabilitySnapshotError, CapabilityState, CapabilityStatus};
+1 -1
View File
@@ -15,7 +15,7 @@
use std::fmt; use std::fmt;
use std::sync::Arc; use std::sync::Arc;
use rustfs_filemeta::{ use crate::replication::{
ReplicationState, ReplicationStatusType, VersionPurgeStatusType, replication_statuses_map, version_purge_statuses_map, ReplicationState, ReplicationStatusType, VersionPurgeStatusType, replication_statuses_map, version_purge_statuses_map,
}; };
use time::OffsetDateTime; use time::OffsetDateTime;
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub(crate) use rustfs_filemeta::{
ReplicationState, ReplicationStatusType, VersionPurgeStatusType, replication_statuses_map, version_purge_statuses_map,
};
+6
View File
@@ -29,6 +29,12 @@ Contract crates must stay below implementation crates. Initial forbidden edges:
- `extension-schema -> rustfs` - `extension-schema -> rustfs`
- `extension-schema -> ecstore` - `extension-schema -> ecstore`
`rustfs-storage-api` may only expose storage-facing replication status/state
contracts through `crates/storage-api/src/replication.rs` while the underlying
wire types still live in `rustfs-filemeta`. This keeps the temporary dependency
centralized until those wire contracts can move without introducing a
`rustfs-replication` / `rustfs-storage-api` cycle.
Existing migration checks live in: Existing migration checks live in:
- `scripts/check_layer_dependencies.sh` - `scripts/check_layer_dependencies.sh`
@@ -125,6 +125,9 @@ Current coupling:
- resync options, bucket/target resync status DTOs, status display labels, and - resync options, bucket/target resync status DTOs, status display labels, and
the persisted resync status wire format live in `crates/replication`, with the persisted resync status wire format live in `crates/replication`, with
ECStore retaining only error mapping and MRF persistence locally; ECStore retaining only error mapping and MRF persistence locally;
- storage-api delete replication status/state helpers use the local
`crates/storage-api/src/replication.rs` contract boundary while the
underlying wire types remain in `rustfs-filemeta`;
- admin replication extension target filtering and resync request construction - admin replication extension target filtering and resync request construction
stay behind the admin storage boundary instead of exposing replication work stay behind the admin storage boundary instead of exposing replication work
DTO construction to handlers; DTO construction to handlers;
@@ -168,6 +171,10 @@ Required contracts before crate movement:
`crates/ecstore/src/bucket/replication/replication_filemeta_boundary.rs`, `crates/ecstore/src/bucket/replication/replication_filemeta_boundary.rs`,
while `FileInfo` remains in the storage boundary for storage trait bindings while `FileInfo` remains in the storage boundary for storage trait bindings
and walk options. and walk options.
- `StorageApiReplicationContracts`: storage-api delete DTO replication
state/status helpers are concentrated in `crates/storage-api/src/replication.rs`
until the underlying wire contracts can move without a
`rustfs-replication` / `rustfs-storage-api` dependency cycle.
- `ReplicationErrorBoundary`: ECStore error/result contracts and - `ReplicationErrorBoundary`: ECStore error/result contracts and
replication-specific error classifiers. `crate::error` imports are replication-specific error classifiers. `crate::error` imports are
concentrated in concentrated in
@@ -84,6 +84,7 @@ require_source_contains "docs/architecture/obs-ecstore-dependency-inventory.md"
require_source_contains "docs/architecture/obs-ecstore-dependency-inventory.md" "crates/obs/src/metrics/storage_api.rs" "observability ECStore storage_api boundary" require_source_contains "docs/architecture/obs-ecstore-dependency-inventory.md" "crates/obs/src/metrics/storage_api.rs" "observability ECStore storage_api boundary"
require_source_contains "docs/architecture/overview.md" "ecstore-api-facade-inventory.md" "architecture overview ECStore facade inventory link" require_source_contains "docs/architecture/overview.md" "ecstore-api-facade-inventory.md" "architecture overview ECStore facade inventory link"
require_source_contains "docs/architecture/ecstore-module-split-plan.md" "ecstore-api-facade-inventory.md" "ECStore split plan facade inventory link" require_source_contains "docs/architecture/ecstore-module-split-plan.md" "ecstore-api-facade-inventory.md" "ECStore split plan facade inventory link"
require_source_contains "docs/architecture/ecstore-module-split-plan.md" "StorageApiReplicationContracts" "ECStore split plan storage-api replication contract section"
require_source_contains "docs/architecture/ecstore-api-facade-inventory.md" "## Facade Group Inventory" "ECStore facade inventory group section" require_source_contains "docs/architecture/ecstore-api-facade-inventory.md" "## Facade Group Inventory" "ECStore facade inventory group section"
require_source_contains "docs/architecture/ecstore-api-facade-inventory.md" "## External Consumer Boundaries" "ECStore facade inventory consumer boundary section" require_source_contains "docs/architecture/ecstore-api-facade-inventory.md" "## External Consumer Boundaries" "ECStore facade inventory consumer boundary section"
require_source_contains "docs/architecture/ecstore-api-facade-inventory.md" "## Split Dependency Inventory" "ECStore split dependency inventory section" require_source_contains "docs/architecture/ecstore-api-facade-inventory.md" "## Split Dependency Inventory" "ECStore split dependency inventory section"
@@ -138,6 +139,7 @@ LIFECYCLE_OBJECT_LOCK_BOUNDARY_BYPASS_HITS_FILE="${TMP_DIR}/lifecycle_object_loc
LIFECYCLE_REPLICATION_SINK_BYPASS_HITS_FILE="${TMP_DIR}/lifecycle_replication_sink_bypass_hits.txt" LIFECYCLE_REPLICATION_SINK_BYPASS_HITS_FILE="${TMP_DIR}/lifecycle_replication_sink_bypass_hits.txt"
LIFECYCLE_REPLICATION_CRATE_BYPASS_HITS_FILE="${TMP_DIR}/lifecycle_replication_crate_bypass_hits.txt" LIFECYCLE_REPLICATION_CRATE_BYPASS_HITS_FILE="${TMP_DIR}/lifecycle_replication_crate_bypass_hits.txt"
ECSTORE_REPLICATION_CONTRACT_BYPASS_HITS_FILE="${TMP_DIR}/ecstore_replication_contract_bypass_hits.txt" ECSTORE_REPLICATION_CONTRACT_BYPASS_HITS_FILE="${TMP_DIR}/ecstore_replication_contract_bypass_hits.txt"
STORAGE_API_REPLICATION_CONTRACT_BYPASS_HITS_FILE="${TMP_DIR}/storage_api_replication_contract_bypass_hits.txt"
STORE_API_EXTERNAL_LIST_CONSUMER_HITS_FILE="${TMP_DIR}/store_api_external_list_consumer_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_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" STORE_API_OBJECT_OPERATION_LOCAL_METHOD_HITS_FILE="${TMP_DIR}/store_api_object_operation_local_method_hits.txt"
@@ -972,6 +974,18 @@ if [[ -s "$ECSTORE_REPLICATION_CONTRACT_BYPASS_HITS_FILE" ]]; then
report_failure "ECStore owner replication contracts must stay behind crates/ecstore/src/bucket/replication: $(paste -sd '; ' "$ECSTORE_REPLICATION_CONTRACT_BYPASS_HITS_FILE")" report_failure "ECStore owner replication contracts must stay behind crates/ecstore/src/bucket/replication: $(paste -sd '; ' "$ECSTORE_REPLICATION_CONTRACT_BYPASS_HITS_FILE")"
fi fi
(
cd "$ROOT_DIR"
rg -n -U --with-filename 'use\s+rustfs_filemeta::\{[^}]*\b(ReplicationState|ReplicationStatusType|VersionPurgeStatusType|replication_statuses_map|version_purge_statuses_map)\b|use\s+rustfs_filemeta::(ReplicationState|ReplicationStatusType|VersionPurgeStatusType|replication_statuses_map|version_purge_statuses_map)\b|rustfs_filemeta::(Replication|VersionPurge|replication_statuses_map|version_purge_statuses_map)' \
crates/storage-api/src \
--glob '*.rs' |
rg -v '^crates/storage-api/src/replication\.rs:' || true
) >"$STORAGE_API_REPLICATION_CONTRACT_BYPASS_HITS_FILE"
if [[ -s "$STORAGE_API_REPLICATION_CONTRACT_BYPASS_HITS_FILE" ]]; then
report_failure "storage-api replication contracts must stay behind crates/storage-api/src/replication.rs: $(paste -sd '; ' "$STORAGE_API_REPLICATION_CONTRACT_BYPASS_HITS_FILE")"
fi
( (
cd "$ROOT_DIR" cd "$ROOT_DIR"
rg -n --no-heading 'rustfs_ecstore::store_api(?:::\{[^}]*\b(?:ListObjectVersionsInfo|ListObjectsV2Info|ObjectInfoOrErr)\b|::(?:ListObjectVersionsInfo|ListObjectsV2Info|ObjectInfoOrErr)\b)' \ rg -n --no-heading 'rustfs_ecstore::store_api(?:::\{[^}]*\b(?:ListObjectVersionsInfo|ListObjectsV2Info|ObjectInfoOrErr)\b|::(?:ListObjectVersionsInfo|ListObjectsV2Info|ObjectInfoOrErr)\b)' \