mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-21 20:06:37 +00:00
6cf9cf7bb5
* chore(ecstore): drop the bucket dead_code blanket The last blanket of the backlog#1823 burn-down, and the largest: 71 items across lifecycle, replication, metadata, quota, object lock and bucket utils. Four are deleted. Deleted, all trivial: - check_valid_object_name and check_valid_object_name_prefix, a pair that only calls into each other with no external caller. Worth stating plainly so nobody reads this as a validation gap: object names are validated through check_object_name_for_length_and_slash, which is live; this pair is a second, unwired entry point. - DEFAULT_HEALTH_CHECK_RELOAD_DURATION, a lone unused constant. - The LifecycleReplicationConfig alias, which orphaned a re-export in replication/mod.rs that goes with it. Everything else is kept, in four groups, because the blanket here was hiding structure rather than rot: Windows platform gating. WINDOWS_RESERVED_NAMES, the two reason constants and object_name_has_windows_incompatible_segment are called from inside the #[cfg(target_os = "windows")] block in check_object_name_for_length_and_slash (utils.rs:228-255), so they only read as dead on non-Windows hosts. As with the Linux gating in the disk root, this cannot be adjudicated locally: cargo check for both x86_64-pc-windows-msvc and x86_64-unknown-linux-gnu fails in the aws-lc-sys build script for want of a cross C toolchain. CI covers both. Declared boundary surface. The *_boundary.rs and *_bridge.rs files carry the replication split plan's contracts, which scripts/check_architecture_migration_rules.sh pins through the EcstoreReplicationBoundaryImports section of the split-plan doc. Their unused items are declarations, not leftovers. test-util seams. ConfigWriteLockProbe with install/wait_until_attempted follows the same pattern as the barriers in the services and set_disk roots. MinIO-parity tier/lifecycle entry points that this port never wired: apply_lifecycle_action, get_transitioned_object_reader, recover_tier_free_versions, delete_object_from_remote_tier, abort_tier_delete_journal_entry and the replication pool's worker-management surface. These are complete, substantial machinery with no caller — the same shape as data_usage's local_snapshot feature. Removing them is a product decision, so they are made explicit here rather than deleted. Verification, four lanes warning-free: default, --tests, --features rio-v2 --tests, --features test-util --tests. cargo nextest run -p rustfs-ecstore 4096 passed; clippy --lib --tests -D warnings clean; make pre-commit exit 0. Note that clippy is what caught the orphaned re-export above: cargo check and pre-commit both treat unused_imports as a warning. Ref rustfs/backlog#1823 (step 2, final root). * chore(ecstore): correct inaccurate dead_code reasons in the bucket root Six items were labelled 'asserted by this file's tests' or as MinIO-parity entry points while having no caller at all - free get_bucket_acl_config and created_at only reach their own live methods (production goes through created_at_in), BucketVersioningSys::get_in, utils::serialize_content and ServiceType have no reference anywhere, and with_transition_queue_env_async is an unused test fixture, not a tier entry point. Name what each one is so the next reader does not assume coverage that is not there. Ref rustfs/backlog#1823.
245 lines
10 KiB
Rust
245 lines
10 KiB
Rust
// 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.
|
|
|
|
use rustfs_common::metrics::IlmAction;
|
|
|
|
use crate::bucket::lifecycle::lifecycle::ObjectOpts;
|
|
use crate::bucket::replication::ReplicationLifecycleBridge;
|
|
pub(crate) use crate::bucket::replication::ReplicationStatusType;
|
|
#[cfg(test)]
|
|
pub(crate) use crate::bucket::replication::VersionPurgeStatusType;
|
|
pub(crate) use crate::bucket::replication::{
|
|
DeleteReplicationConfigSnapshot, ReplicationObjectBridge, replication_state_to_filemeta,
|
|
};
|
|
use crate::storage_api_contracts::object::DeletedObject;
|
|
|
|
#[allow(
|
|
dead_code,
|
|
reason = "declared boundary surface for the ECStore replication split plan; no caller in this port (backlog#1823)"
|
|
)]
|
|
pub(crate) fn has_pending_version_purge(obj: &ObjectOpts) -> bool {
|
|
obj.version_purge_status.is_pending()
|
|
}
|
|
|
|
#[allow(
|
|
dead_code,
|
|
reason = "declared boundary surface for the ECStore replication split plan; no caller in this port (backlog#1823)"
|
|
)]
|
|
pub(crate) fn has_pending_object_replication(obj: &ObjectOpts) -> bool {
|
|
replication_status_blocks_lifecycle(&obj.replication_status)
|
|
}
|
|
|
|
#[allow(
|
|
dead_code,
|
|
reason = "declared boundary surface for the ECStore replication split plan; no caller in this port (backlog#1823)"
|
|
)]
|
|
pub(crate) fn has_pending_lifecycle_replication(obj: &ObjectOpts) -> bool {
|
|
has_pending_object_replication(obj) || has_pending_version_purge(obj)
|
|
}
|
|
|
|
pub(crate) fn replication_status_blocks_lifecycle(status: &ReplicationStatusType) -> bool {
|
|
matches!(status, ReplicationStatusType::Pending | ReplicationStatusType::Failed)
|
|
}
|
|
|
|
pub(crate) fn lifecycle_action_waits_for_replication(action: IlmAction) -> bool {
|
|
matches!(
|
|
action,
|
|
IlmAction::DeleteAction
|
|
| IlmAction::DeleteVersionAction
|
|
| IlmAction::DeleteRestoredAction
|
|
| IlmAction::DeleteRestoredVersionAction
|
|
| IlmAction::DeleteAllVersionsAction
|
|
| IlmAction::DelMarkerDeleteAllVersionsAction
|
|
| IlmAction::TransitionAction
|
|
| IlmAction::TransitionVersionAction
|
|
)
|
|
}
|
|
|
|
pub(crate) async fn schedule_delete(bucket: String, delete_object: DeletedObject) {
|
|
ReplicationLifecycleBridge::schedule_delete(bucket, delete_object).await;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::HashMap;
|
|
|
|
use crate::bucket::replication::{DeleteReplicationConfigSnapshot, ReplicationObjectBridge};
|
|
use crate::object_api::{ObjectInfo, ObjectOptions};
|
|
use crate::storage_api_contracts::object::ObjectToDelete;
|
|
use rustfs_common::metrics::IlmAction;
|
|
use s3s::dto::{
|
|
BucketVersioningStatus, DeleteMarkerReplication, DeleteMarkerReplicationStatus, DeleteReplication,
|
|
DeleteReplicationStatus, Destination, ReplicationConfiguration, ReplicationRule, ReplicationRuleStatus,
|
|
VersioningConfiguration,
|
|
};
|
|
use uuid::Uuid;
|
|
|
|
use super::*;
|
|
|
|
fn object_opts(version_purge_status: VersionPurgeStatusType) -> ObjectOpts {
|
|
ObjectOpts {
|
|
name: "logs/object".to_string(),
|
|
user_tags: String::new(),
|
|
mod_time: None,
|
|
size: 0,
|
|
version_id: None,
|
|
is_latest: true,
|
|
delete_marker: false,
|
|
num_versions: 1,
|
|
successor_mod_time: None,
|
|
transition_status: String::new(),
|
|
restore_ongoing: false,
|
|
restore_expires: None,
|
|
versioned: true,
|
|
version_suspended: false,
|
|
user_defined: HashMap::new(),
|
|
version_purge_status,
|
|
replication_status: ReplicationStatusType::default(),
|
|
}
|
|
}
|
|
|
|
fn object_opts_with_replication_status(replication_status: ReplicationStatusType) -> ObjectOpts {
|
|
ObjectOpts {
|
|
replication_status,
|
|
..object_opts(VersionPurgeStatusType::default())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn pending_version_purge_blocks_lifecycle_actions_from_object_state() {
|
|
assert!(has_pending_version_purge(&object_opts(VersionPurgeStatusType::Pending)));
|
|
assert!(has_pending_version_purge(&object_opts(VersionPurgeStatusType::Failed)));
|
|
assert!(!has_pending_version_purge(&object_opts(VersionPurgeStatusType::Complete)));
|
|
assert!(!has_pending_version_purge(&object_opts(VersionPurgeStatusType::default())));
|
|
}
|
|
|
|
#[test]
|
|
fn pending_replication_status_blocks_lifecycle_actions_from_object_state() {
|
|
assert!(has_pending_object_replication(&object_opts_with_replication_status(
|
|
ReplicationStatusType::Pending
|
|
)));
|
|
assert!(has_pending_object_replication(&object_opts_with_replication_status(
|
|
ReplicationStatusType::Failed
|
|
)));
|
|
assert!(!has_pending_object_replication(&object_opts_with_replication_status(
|
|
ReplicationStatusType::Completed
|
|
)));
|
|
assert!(!has_pending_object_replication(&object_opts_with_replication_status(
|
|
ReplicationStatusType::Empty
|
|
)));
|
|
}
|
|
|
|
#[test]
|
|
fn lifecycle_action_waits_for_replication_for_expiration_and_transition() {
|
|
assert!(lifecycle_action_waits_for_replication(IlmAction::DeleteAction));
|
|
assert!(lifecycle_action_waits_for_replication(IlmAction::DeleteVersionAction));
|
|
assert!(lifecycle_action_waits_for_replication(IlmAction::TransitionAction));
|
|
assert!(lifecycle_action_waits_for_replication(IlmAction::TransitionVersionAction));
|
|
assert!(!lifecycle_action_waits_for_replication(IlmAction::NoneAction));
|
|
}
|
|
|
|
#[test]
|
|
fn lifecycle_delete_admission_uses_marker_and_version_switches_for_all_purges() {
|
|
for marker_enabled in [false, true] {
|
|
for purge_enabled in [false, true] {
|
|
let snapshot = DeleteReplicationConfigSnapshot::from_configs_for_test(
|
|
VersioningConfiguration {
|
|
status: Some(BucketVersioningStatus::from_static(BucketVersioningStatus::ENABLED)),
|
|
..Default::default()
|
|
},
|
|
Some(ReplicationConfiguration {
|
|
role: String::new(),
|
|
rules: vec![ReplicationRule {
|
|
delete_marker_replication: Some(DeleteMarkerReplication {
|
|
status: Some(if marker_enabled {
|
|
DeleteMarkerReplicationStatus::from_static(DeleteMarkerReplicationStatus::ENABLED)
|
|
} else {
|
|
DeleteMarkerReplicationStatus::from_static(DeleteMarkerReplicationStatus::DISABLED)
|
|
}),
|
|
}),
|
|
delete_replication: Some(DeleteReplication {
|
|
status: if purge_enabled {
|
|
DeleteReplicationStatus::from_static(DeleteReplicationStatus::ENABLED)
|
|
} else {
|
|
DeleteReplicationStatus::from_static(DeleteReplicationStatus::DISABLED)
|
|
},
|
|
}),
|
|
destination: Destination {
|
|
bucket: "arn:rustfs:replication:target".to_string(),
|
|
..Default::default()
|
|
},
|
|
existing_object_replication: None,
|
|
filter: None,
|
|
id: Some("lifecycle-delete-switches".to_string()),
|
|
prefix: Some(String::new()),
|
|
priority: Some(1),
|
|
source_selection_criteria: None,
|
|
status: ReplicationRuleStatus::from_static(ReplicationRuleStatus::ENABLED),
|
|
}],
|
|
}),
|
|
);
|
|
let source = ObjectInfo {
|
|
bucket: "bucket".to_string(),
|
|
name: "logs/object".to_string(),
|
|
..Default::default()
|
|
};
|
|
let marker = ObjectToDelete {
|
|
object_name: source.name.clone(),
|
|
..Default::default()
|
|
};
|
|
let marker_opts = ObjectOptions {
|
|
versioned: true,
|
|
..Default::default()
|
|
};
|
|
assert_eq!(
|
|
ReplicationObjectBridge::check_delete_with_snapshot(&marker, &source, &marker_opts, false, &snapshot)
|
|
.replicate_any(),
|
|
marker_enabled
|
|
);
|
|
|
|
for delete_marker in [false, true] {
|
|
for version_id in [Uuid::new_v4(), Uuid::nil()] {
|
|
let purge = ObjectToDelete {
|
|
object_name: source.name.clone(),
|
|
version_id: Some(version_id),
|
|
..Default::default()
|
|
};
|
|
let purge_source = ObjectInfo {
|
|
delete_marker,
|
|
..source.clone()
|
|
};
|
|
let purge_opts = ObjectOptions {
|
|
version_id: Some(version_id.to_string()),
|
|
versioned: true,
|
|
..Default::default()
|
|
};
|
|
assert_eq!(
|
|
ReplicationObjectBridge::check_delete_with_snapshot(
|
|
&purge,
|
|
&purge_source,
|
|
&purge_opts,
|
|
false,
|
|
&snapshot,
|
|
)
|
|
.replicate_any(),
|
|
purge_enabled,
|
|
"delete marker={delete_marker}, version_id={version_id}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|