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
+567 -1
View File
@@ -41,7 +41,7 @@ use crate::error::{Error, Result, StorageError};
use crate::services::tier::{
tier_admin::TierCreds,
tier_config::{TierConfig, TierType},
tier_handlers::{ERR_TIER_ALREADY_EXISTS, ERR_TIER_NAME_NOT_UPPERCASE, ERR_TIER_NOT_FOUND},
tier_handlers::{ERR_TIER_ALREADY_EXISTS, ERR_TIER_NAME_NOT_UPPERCASE, ERR_TIER_NOT_FOUND, ERR_TIER_RESERVED_NAME},
warm_backend::{check_warm_backend, new_warm_backend},
};
use crate::storage_api_contracts::{
@@ -763,6 +763,16 @@ impl TierConfigMgr {
return Err(ERR_TIER_NAME_NOT_UPPERCASE.clone());
}
// Reject AWS/MinIO reserved storage-class names as remote-tier names.
// MinIO reserves STANDARD and REDUCED_REDUNDANCY (RRS); a tier must not
// shadow them. The comparison is case-insensitive to match MinIO parity
// and to stay robust regardless of the uppercase gate above.
if tier_name.eq_ignore_ascii_case(crate::config::storageclass::STANDARD)
|| tier_name.eq_ignore_ascii_case(crate::config::storageclass::RRS)
{
return Err(ERR_TIER_RESERVED_NAME.clone());
}
let (_, b) = self.is_tier_name_in_use(tier_name);
if b {
return Err(ERR_TIER_ALREADY_EXISTS.clone());
@@ -1445,4 +1455,560 @@ mod tests {
assert!(rendered.contains("failed to save tiering config"), "{rendered}");
assert!(rendered.contains("object layer not initialized"), "{rendered}");
}
// ---------------------------------------------------------------------
// State-machine coverage (ilm-4): add / edit / remove / verify.
//
// These tests must not reach a real remote tier. Two techniques keep them
// hermetic:
// * error paths that return *before* `new_warm_backend` constructs a
// client (name validation, duplicate detection, unsupported type,
// missing backend payload, missing credentials);
// * a `MockWarmBackend` injected directly into `driver_cache`, so
// `get_driver` returns it from cache without any network I/O, which
// lets us drive `remove`/`verify` through every branch.
// ---------------------------------------------------------------------
use crate::client::transition_api::{ReadCloser, ReaderImpl};
use crate::services::tier::warm_backend::{WarmBackend, WarmBackendGetOpts};
fn empty_mgr() -> TierConfigMgr {
TierConfigMgr {
driver_cache: HashMap::new(),
tiers: HashMap::new(),
last_refreshed_at: OffsetDateTime::now_utc(),
}
}
fn build_rustfs_tier(name: &str) -> TierConfig {
TierConfig {
version: "v1".to_string(),
tier_type: TierType::RustFS,
name: name.to_string(),
rustfs: Some(crate::services::tier::tier_config::TierRustFS {
name: name.to_string(),
endpoint: "https://example-compat.invalid".to_string(),
access_key: "ak".to_string(),
secret_key: "sk".to_string(),
bucket: "bucket-r".to_string(),
prefix: "prefix-r".to_string(),
region: "us-east-1".to_string(),
storage_class: "STANDARD".to_string(),
}),
..Default::default()
}
}
/// A fully offline `WarmBackend` used to exercise the driver-facing
/// branches of `remove`/`verify` without touching a remote tier.
struct MockWarmBackend {
/// `Some(b)` -> `in_use()` returns `Ok(b)`; `None` -> returns `Err`.
in_use_value: Option<bool>,
/// When false, put/get/remove all fail (drives `verify` error paths).
healthy: bool,
}
#[async_trait::async_trait]
impl WarmBackend for MockWarmBackend {
async fn put(&self, _object: &str, _r: ReaderImpl, _length: i64) -> std::result::Result<String, std::io::Error> {
if self.healthy {
Ok("mock-version".to_string())
} else {
Err(std::io::Error::other("mock put failed"))
}
}
async fn put_with_meta(
&self,
object: &str,
r: ReaderImpl,
length: i64,
_meta: HashMap<String, String>,
) -> std::result::Result<String, std::io::Error> {
self.put(object, r, length).await
}
async fn get(
&self,
_object: &str,
_rv: &str,
_opts: WarmBackendGetOpts,
) -> std::result::Result<ReadCloser, std::io::Error> {
if self.healthy {
Ok(BufReader::new(Cursor::new(b"RustFS".to_vec())))
} else {
Err(std::io::Error::other("mock get failed"))
}
}
async fn remove(&self, _object: &str, _rv: &str) -> std::result::Result<(), std::io::Error> {
if self.healthy {
Ok(())
} else {
Err(std::io::Error::other("mock remove failed"))
}
}
async fn in_use(&self) -> std::result::Result<bool, std::io::Error> {
match self.in_use_value {
Some(b) => Ok(b),
None => Err(std::io::Error::other("mock in_use failed")),
}
}
}
fn inject_mock(mgr: &mut TierConfigMgr, name: &str, tier: TierConfig, mock: MockWarmBackend) {
mgr.tiers.insert(name.to_string(), tier);
mgr.driver_cache.insert(name.to_string(), Box::new(mock));
}
// ---- add ------------------------------------------------------------
#[tokio::test]
async fn test_add_rejects_non_uppercase_name() {
let mut mgr = empty_mgr();
let err = mgr
.add(build_s3_tier("cold-a"), true)
.await
.expect_err("lowercase tier name must be rejected");
assert_eq!(err.code, ERR_TIER_NAME_NOT_UPPERCASE.code);
assert!(mgr.tiers.is_empty(), "rejected tier must not be persisted");
}
#[tokio::test]
async fn test_add_rejects_duplicate_name() {
let mut mgr = empty_mgr();
mgr.tiers.insert("COLD-A".to_string(), build_s3_tier("COLD-A"));
let err = mgr
.add(build_s3_tier("COLD-A"), true)
.await
.expect_err("duplicate tier name must be rejected");
assert_eq!(err.code, ERR_TIER_ALREADY_EXISTS.code);
}
#[tokio::test]
async fn test_add_rejects_unsupported_tier_type() {
let mut mgr = empty_mgr();
// `Unsupported` is rejected by `new_warm_backend` before any network I/O.
let tier = TierConfig {
version: "v1".to_string(),
tier_type: TierType::Unsupported,
name: "COLD-U".to_string(),
..Default::default()
};
let err = mgr.add(tier, true).await.expect_err("unsupported tier type must be rejected");
assert_eq!(err.code, ERR_TIER_TYPE_UNSUPPORTED.code);
assert!(mgr.tiers.is_empty());
}
#[tokio::test]
async fn test_add_rejects_missing_backend_payload() {
let mut mgr = empty_mgr();
// Declares S3 type but omits the S3 payload; rejected before client build.
let tier = TierConfig {
version: "v1".to_string(),
tier_type: TierType::S3,
name: "COLD-A".to_string(),
s3: None,
..Default::default()
};
let err = mgr
.add(tier, true)
.await
.expect_err("missing backend payload must be rejected");
assert_eq!(err.code, "XRustFSAdminTierInvalidConfig");
assert!(err.message.contains("S3 tier configuration not found"), "{}", err.message);
}
#[tokio::test]
async fn test_add_rejects_reserved_names() {
// Supersedes the former `test_add_does_not_reserve_standard_name_regression_anchor`
// (added by PR #4713 to pin the pre-fix behavior). RustFS now rejects the
// AWS/MinIO reserved storage-class names STANDARD and REDUCED_REDUNDANCY
// (RRS) as remote-tier names, matching MinIO parity.
//
// `tier_type` is deliberately `Unsupported`: before the guard existed,
// "STANDARD" reached `new_warm_backend` and failed with
// `ERR_TIER_TYPE_UNSUPPORTED`. Asserting `ERR_TIER_RESERVED_NAME` here
// proves the reserved-name guard fires *before* the type/backend check.
for reserved in [crate::config::storageclass::STANDARD, crate::config::storageclass::RRS] {
let mut mgr = empty_mgr();
let tier = TierConfig {
version: "v1".to_string(),
tier_type: TierType::Unsupported,
name: reserved.to_string(),
..Default::default()
};
let err = mgr.add(tier, true).await.expect_err("reserved tier name must be rejected");
assert_eq!(
err.code, ERR_TIER_RESERVED_NAME.code,
"reserved tier name {reserved} must be rejected before the type check"
);
assert!(mgr.tiers.is_empty(), "rejected reserved tier {reserved} must not be persisted");
}
}
// ---- edit -----------------------------------------------------------
#[tokio::test]
async fn test_edit_rejects_unknown_tier() {
let mut mgr = empty_mgr();
let err = mgr
.edit("NOPE", TierCreds::default())
.await
.expect_err("editing an unknown tier must fail");
assert_eq!(err.code, ERR_TIER_NOT_FOUND.code);
}
#[tokio::test]
async fn test_edit_rejects_missing_credentials_for_rustfs() {
let mut mgr = empty_mgr();
mgr.tiers.insert("COLD-R".to_string(), build_rustfs_tier("COLD-R"));
// Empty access/secret keys => rejected before any driver rebuild.
let err = mgr
.edit("COLD-R", TierCreds::default())
.await
.expect_err("empty credentials must be rejected");
assert_eq!(err.code, ERR_TIER_MISSING_CREDENTIALS.code);
}
#[tokio::test]
async fn test_edit_rejects_missing_credentials_for_minio() {
let mut mgr = empty_mgr();
let tier = TierConfig {
version: "v1".to_string(),
tier_type: TierType::MinIO,
name: "COLD-M".to_string(),
minio: Some(crate::services::tier::tier_config::TierMinIO {
name: "COLD-M".to_string(),
endpoint: "https://example-compat.invalid".to_string(),
access_key: "ak".to_string(),
secret_key: "sk".to_string(),
bucket: "bucket-m".to_string(),
prefix: String::new(),
region: String::new(),
}),
..Default::default()
};
mgr.tiers.insert("COLD-M".to_string(), tier);
let err = mgr
.edit("COLD-M", TierCreds::default())
.await
.expect_err("empty credentials must be rejected");
assert_eq!(err.code, ERR_TIER_MISSING_CREDENTIALS.code);
}
// ---- remove ---------------------------------------------------------
#[tokio::test]
async fn test_remove_unknown_tier_is_idempotent() {
let mut mgr = empty_mgr();
// Unknown tier: get_driver returns NotFound, which `remove` swallows.
mgr.remove("NOPE", false).await.expect("removing an unknown tier is a no-op");
}
#[tokio::test]
async fn test_remove_rejects_backend_in_use() {
let mut mgr = empty_mgr();
inject_mock(
&mut mgr,
"COLD-A",
build_s3_tier("COLD-A"),
MockWarmBackend {
in_use_value: Some(true),
healthy: true,
},
);
let err = mgr
.remove("COLD-A", false)
.await
.expect_err("in-use backend must not be removed");
assert_eq!(err.code, ERR_TIER_BACKEND_NOT_EMPTY.code);
assert!(mgr.tiers.contains_key("COLD-A"), "tier must survive a rejected remove");
assert!(mgr.driver_cache.contains_key("COLD-A"));
}
#[tokio::test]
async fn test_remove_succeeds_when_backend_empty() {
let mut mgr = empty_mgr();
inject_mock(
&mut mgr,
"COLD-A",
build_s3_tier("COLD-A"),
MockWarmBackend {
in_use_value: Some(false),
healthy: true,
},
);
mgr.remove("COLD-A", false).await.expect("empty backend can be removed");
assert!(!mgr.tiers.contains_key("COLD-A"));
assert!(!mgr.driver_cache.contains_key("COLD-A"));
}
#[tokio::test]
async fn test_remove_force_skips_in_use_probe() {
let mut mgr = empty_mgr();
// in_use_value: None would error if probed; force=true must skip it.
inject_mock(
&mut mgr,
"COLD-A",
build_s3_tier("COLD-A"),
MockWarmBackend {
in_use_value: None,
healthy: true,
},
);
mgr.remove("COLD-A", true).await.expect("force remove must not probe in_use");
assert!(!mgr.tiers.contains_key("COLD-A"));
assert!(!mgr.driver_cache.contains_key("COLD-A"));
}
#[tokio::test]
async fn test_remove_reports_probe_error() {
let mut mgr = empty_mgr();
inject_mock(
&mut mgr,
"COLD-A",
build_s3_tier("COLD-A"),
MockWarmBackend {
in_use_value: None,
healthy: true,
},
);
let err = mgr
.remove("COLD-A", false)
.await
.expect_err("a failed in_use probe must surface as an error");
assert_eq!(err.code, ERR_TIER_PERM_ERR.code);
assert!(mgr.tiers.contains_key("COLD-A"), "tier must survive a failed probe");
}
// ---- verify ---------------------------------------------------------
#[tokio::test]
async fn test_verify_unknown_tier_errors() {
let mut mgr = empty_mgr();
let err = mgr.verify("NOPE").await.expect_err("verifying an unknown tier must fail");
assert!(err.to_string().contains("not found"), "{err}");
}
#[tokio::test]
async fn test_verify_succeeds_with_healthy_backend() {
let mut mgr = empty_mgr();
inject_mock(
&mut mgr,
"COLD-A",
build_s3_tier("COLD-A"),
MockWarmBackend {
in_use_value: Some(false),
healthy: true,
},
);
mgr.verify("COLD-A").await.expect("healthy backend must verify");
}
#[tokio::test]
async fn test_verify_reports_backend_failure() {
let mut mgr = empty_mgr();
inject_mock(
&mut mgr,
"COLD-A",
build_s3_tier("COLD-A"),
MockWarmBackend {
in_use_value: Some(false),
healthy: false,
},
);
mgr.verify("COLD-A")
.await
.expect_err("an unhealthy backend must fail verification");
}
// ---- pure query helpers --------------------------------------------
#[test]
fn test_query_helpers_reflect_membership() {
let mut mgr = empty_mgr();
assert!(mgr.empty());
assert!(!mgr.is_tier_valid("COLD-A"));
assert_eq!(mgr.tier_type("COLD-A"), "internal");
assert!(mgr.get("COLD-A").is_none());
mgr.tiers.insert("COLD-A".to_string(), build_s3_tier("COLD-A"));
assert!(!mgr.empty());
assert!(mgr.is_tier_valid("COLD-A"));
let (ty, in_use) = mgr.is_tier_name_in_use("COLD-A");
assert!(in_use);
assert_eq!(ty.as_lowercase(), "s3");
assert_eq!(mgr.tier_type("COLD-A"), "s3");
assert_eq!(mgr.list_tiers().len(), 1);
assert!(mgr.get("COLD-A").is_some());
}
// ---- persistence: encode/decode and legacy migration ---------------
#[test]
fn test_marshal_unmarshal_json_roundtrip_preserves_tier() {
let mut mgr = empty_mgr();
mgr.tiers.insert("COLD-A".to_string(), build_s3_tier("COLD-A"));
let bytes = mgr.marshal().expect("marshal should succeed");
let decoded = TierConfigMgr::unmarshal(&bytes).expect("unmarshal should succeed");
let tier = decoded.tiers.get("COLD-A").expect("tier survives json roundtrip");
assert_eq!(tier.tier_type.as_lowercase(), "s3");
let s3 = tier.s3.as_ref().expect("s3 payload survives");
assert_eq!(s3.bucket, "bucket-a");
// secret_key is a serialized field (unlike Clone, marshal does not redact).
assert_eq!(s3.secret_key, "sk");
}
#[test]
fn test_external_blob_roundtrip_azure_maps_account_fields() {
let mut mgr = empty_mgr();
mgr.tiers.insert(
"COLD-AZ".to_string(),
TierConfig {
version: "v1".to_string(),
tier_type: TierType::Azure,
name: "COLD-AZ".to_string(),
azure: Some(crate::services::tier::tier_config::TierAzure {
name: "COLD-AZ".to_string(),
endpoint: "https://example-azure.invalid".to_string(),
access_key: "account-name".to_string(),
secret_key: "account-key".to_string(),
bucket: "container".to_string(),
prefix: "p".to_string(),
region: "eastus".to_string(),
storage_class: "HOT".to_string(),
sp_auth: crate::services::tier::tier_config::ServicePrincipalAuth {
tenant_id: "tenant".to_string(),
client_id: "client".to_string(),
client_secret: "secret".to_string(),
},
}),
..Default::default()
},
);
let bytes = encode_external_tiering_config_blob(&mgr).expect("encode azure tier");
let decoded = decode_external_tiering_config_blob(&bytes).expect("decode azure tier");
let tier = decoded.tiers.get("COLD-AZ").expect("azure tier survives roundtrip");
assert_eq!(tier.tier_type.as_lowercase(), "azure");
let az = tier.azure.as_ref().expect("azure payload survives");
// AccountName/AccountKey in the on-disk format map back to access/secret.
assert_eq!(az.access_key, "account-name");
assert_eq!(az.secret_key, "account-key");
assert_eq!(az.sp_auth.tenant_id, "tenant");
assert_eq!(az.sp_auth.client_secret, "secret");
}
#[test]
fn test_external_blob_roundtrip_gcs_preserves_creds() {
let mut mgr = empty_mgr();
mgr.tiers.insert(
"COLD-G".to_string(),
TierConfig {
version: "v1".to_string(),
tier_type: TierType::GCS,
name: "COLD-G".to_string(),
gcs: Some(crate::services::tier::tier_config::TierGCS {
name: "COLD-G".to_string(),
endpoint: "https://storage.googleapis.com".to_string(),
creds: "service-account-json".to_string(),
bucket: "gbucket".to_string(),
prefix: "gp".to_string(),
region: "us".to_string(),
storage_class: "NEARLINE".to_string(),
}),
..Default::default()
},
);
let bytes = encode_external_tiering_config_blob(&mgr).expect("encode gcs tier");
let decoded = decode_external_tiering_config_blob(&bytes).expect("decode gcs tier");
let tier = decoded.tiers.get("COLD-G").expect("gcs tier survives roundtrip");
assert_eq!(tier.tier_type.as_lowercase(), "gcs");
assert_eq!(tier.gcs.as_ref().expect("gcs payload survives").creds, "service-account-json");
}
// `TierConfigMgr` intentionally does not derive `Debug` (it holds live
// driver handles), so `Result::expect_err` cannot be used on decode
// results. Extract the error explicitly instead.
fn expect_decode_err(data: &[u8]) -> std::io::Error {
match decode_external_tiering_config_blob(data) {
Ok(_) => panic!("expected decode to fail"),
Err(err) => err,
}
}
#[test]
fn test_external_blob_rejects_truncated_data() {
let err = expect_decode_err(&[0u8; 3]);
assert!(err.to_string().contains("no data"), "{err}");
}
#[test]
fn test_external_blob_rejects_unknown_format() {
// Valid length, but a format word the decoder does not recognise.
let mut data = Vec::new();
data.extend_from_slice(&99u16.to_le_bytes());
data.extend_from_slice(&TIER_CONFIG_VERSION.to_le_bytes());
data.extend_from_slice(&[0u8; 8]);
let err = expect_decode_err(&data);
assert!(err.to_string().contains("unknown format"), "{err}");
}
#[test]
fn test_external_blob_rejects_unknown_version() {
let mut data = Vec::new();
data.extend_from_slice(&TIER_CONFIG_FORMAT.to_le_bytes());
data.extend_from_slice(&99u16.to_le_bytes());
data.extend_from_slice(&[0u8; 8]);
let err = expect_decode_err(&data);
assert!(err.to_string().contains("unknown version"), "{err}");
}
#[test]
fn test_external_blob_accepts_legacy_v1_version_word() {
// The decoder must keep accepting the historical v1 version word, not
// just the current TIER_CONFIG_VERSION, so old on-disk configs load.
let mut mgr = empty_mgr();
mgr.tiers.insert("COLD-A".to_string(), build_s3_tier("COLD-A"));
let current = encode_external_tiering_config_blob(&mgr).expect("encode");
// Rewrite the version word (bytes 2..4) from VERSION to V1.
let mut legacy = current.to_vec();
legacy[2..4].copy_from_slice(&TIER_CONFIG_V1.to_le_bytes());
let decoded = decode_external_tiering_config_blob(&legacy).expect("v1 version word must decode");
assert!(decoded.tiers.contains_key("COLD-A"));
}
#[test]
fn test_encode_external_blob_errors_on_missing_payload() {
let mut mgr = empty_mgr();
mgr.tiers.insert(
"COLD-A".to_string(),
TierConfig {
version: "v1".to_string(),
tier_type: TierType::S3,
name: "COLD-A".to_string(),
s3: None,
..Default::default()
},
);
let err = encode_external_tiering_config_blob(&mgr).expect_err("missing payload must fail encode");
assert!(err.to_string().contains("missing s3 backend payload"), "{err}");
}
}
+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,
};
}