test(ecstore): add TierConfigMgr state-machine unit coverage (#4713)

* test(ecstore): unit-test TierConfigMgr add/edit/remove/verify state machine (backlog#1148 ilm-4)

Covers the tier config state machine and persistence paths that previously
had only 4 codec tests and none for tier_config.rs:

- add: non-uppercase name, duplicate name, unsupported type, missing
  backend payload, and a regression anchor documenting that AWS-reserved
  names (STANDARD) are not currently rejected.
- edit: unknown tier, missing-credentials rejection for RustFS and MinIO.
- remove: idempotent unknown-tier no-op, in-use rejection, empty-backend
  success, force skips the in_use probe, and probe-error surfacing.
- verify: unknown tier, healthy backend, unhealthy backend.
- pure query helpers (empty/is_tier_valid/tier_type/get/list_tiers).
- persistence: JSON marshal/unmarshal roundtrip, external tier-config.bin
  roundtrip for Azure and GCS payload mapping, truncated/unknown-format/
  unknown-version rejection, legacy v1 version-word acceptance, and encode
  failure on missing payload.

Tests are hermetic: error paths return before backend construction, and a
MockWarmBackend injected into driver_cache exercises remove/verify without
any real remote. Refs backlog#1155.

Co-authored-by: overtrue <anzhengchao@gmail.com>

* fix(tier): reject reserved names STANDARD/RRS in TierConfigMgr::add (backlog#1148 ilm-4) (#4721)
This commit is contained in:
Zhengchao An
2026-07-11 13:31:30 +08:00
committed by GitHub
parent 846aa95c32
commit 05fae6f939
3 changed files with 582 additions and 23 deletions
+13 -18
View File
@@ -17,7 +17,7 @@ use crate::admin::runtime_sources::object_store_from_extensions;
use crate::admin::storage_api::tier::{
AdminError, DailyAllTierStats, ERR_TIER_ALREADY_EXISTS, ERR_TIER_BACKEND_IN_USE, ERR_TIER_BACKEND_NOT_EMPTY,
ERR_TIER_CONNECT_ERR, ERR_TIER_INVALID_CREDENTIALS, ERR_TIER_MISSING_CREDENTIALS, ERR_TIER_NAME_NOT_UPPERCASE,
ERR_TIER_NOT_FOUND, TierConfig, TierCreds, TierType, storageclass,
ERR_TIER_NOT_FOUND, ERR_TIER_RESERVED_NAME, TierConfig, TierCreds, TierType,
};
use crate::{
admin::runtime_sources::{current_daily_tier_stats, current_notification_system, current_tier_config_handle},
@@ -311,22 +311,6 @@ impl Operation for AddTier {
s3_error!(InvalidRequest, "invalid force flag")
})?;
}
match args.name.as_str() {
storageclass::STANDARD | storageclass::RRS => {
warn!(
event = EVENT_ADMIN_TIER_STATE,
component = LOG_COMPONENT_ADMIN,
subsystem = LOG_SUBSYSTEM_TIER,
action = "add_tier",
tier_name = %args.name,
result = "reserved_name_rejected",
"admin tier state"
);
return Err(s3_error!(InvalidRequest, "Cannot use reserved tier name"));
}
&_ => (),
}
let Some(store) = object_store_from_extensions(&req.extensions) else {
return Err(s3_error!(InternalError, "object store is not initialized"));
};
@@ -350,7 +334,18 @@ impl Operation for AddTier {
));
}
if let Err(err) = tier_config_mgr.add(args, force).await {
return if err.code == ERR_TIER_ALREADY_EXISTS.code {
return if err.code == ERR_TIER_RESERVED_NAME.code {
warn!(
event = EVENT_ADMIN_TIER_STATE,
component = LOG_COMPONENT_ADMIN,
subsystem = LOG_SUBSYSTEM_TIER,
action = "add_tier",
tier_name = %tier_name_for_log,
result = "reserved_name_rejected",
"admin tier state"
);
Err(s3_error!(InvalidRequest, "Cannot use reserved tier name"))
} else if err.code == ERR_TIER_ALREADY_EXISTS.code {
Err(S3Error::with_message(
S3ErrorCode::Custom("TierNameAlreadyExist".into()),
"tier name already exists",
+2 -4
View File
@@ -378,9 +378,7 @@ pub(crate) mod versioning_sys {
pub(crate) mod storageclass {
pub(crate) const INLINE_BLOCK_ENV: &str = super::ecstore_config::storageclass::INLINE_BLOCK_ENV;
pub(crate) const OPTIMIZE_ENV: &str = super::ecstore_config::storageclass::OPTIMIZE_ENV;
pub(crate) const RRS: &str = super::ecstore_config::storageclass::RRS;
pub(crate) const RRS_ENV: &str = super::ecstore_config::storageclass::RRS_ENV;
pub(crate) const STANDARD: &str = super::ecstore_config::storageclass::STANDARD;
pub(crate) const STANDARD_ENV: &str = super::ecstore_config::storageclass::STANDARD_ENV;
pub(crate) type Config = super::ecstore_config::storageclass::Config;
@@ -449,6 +447,7 @@ pub(crate) static ERR_TIER_INVALID_CREDENTIALS: AdminErrorRef =
pub(crate) static ERR_TIER_NAME_NOT_UPPERCASE: AdminErrorRef =
AdminErrorRef(|| &ecstore_tier::tier_handlers::ERR_TIER_NAME_NOT_UPPERCASE);
pub(crate) static ERR_TIER_NOT_FOUND: AdminErrorRef = AdminErrorRef(|| &ecstore_tier::tier_handlers::ERR_TIER_NOT_FOUND);
pub(crate) static ERR_TIER_RESERVED_NAME: AdminErrorRef = AdminErrorRef(|| &ecstore_tier::tier_handlers::ERR_TIER_RESERVED_NAME);
pub(crate) mod data_usage {
use std::sync::Arc;
@@ -584,10 +583,9 @@ pub(crate) mod runtime {
}
pub(crate) mod tier {
pub(crate) use super::storageclass;
pub(crate) use super::{
AdminError, DailyAllTierStats, ERR_TIER_ALREADY_EXISTS, ERR_TIER_BACKEND_IN_USE, ERR_TIER_BACKEND_NOT_EMPTY,
ERR_TIER_CONNECT_ERR, ERR_TIER_INVALID_CREDENTIALS, ERR_TIER_MISSING_CREDENTIALS, ERR_TIER_NAME_NOT_UPPERCASE,
ERR_TIER_NOT_FOUND, TierConfig, TierCreds, TierType,
ERR_TIER_NOT_FOUND, ERR_TIER_RESERVED_NAME, TierConfig, TierCreds, TierType,
};
}