mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-13 16:46:55 +00:00
feat(admin): restore config admin compatibility (#3133)
* feat(admin): restore config admin compatibility Co-authored-by: weisd <im@weisd.in> * fix(admin): align config admin clean rebuild Co-authored-by: weisd <im@weisd.in> * fix(admin): align config history and peer signals * fix(admin): harden config admin mutations * fix(admin): tighten config review follow-ups * perf(admin): reuse env snapshot in config render * fix(ecstore): clean up config admin and listing error handling Remove redundant is_all_volume_not_found check in list_merged, add storage class encode/decode roundtrip tests, fresh boot integration test, and config admin clean rebuild improvements. Co-authored-by: hehutu <heihutu@gmail.com> * fix(admin): sync global server config on mutation and reload Change GLOBAL_SERVER_CONFIG from OnceLock to RwLock so config mutations (set/del/restore/reload) are visible to readers without restart. Call set_global_server_config after every store save and on snapshot reload. Register storage_class as a dynamic config subsystem. Co-authored-by: hehutu <heihutu@gmail.com> * style: apply rustfmt to config and admin tests Co-authored-by: hehutu <heihutu@gmail.com> * fix(test): update signal_service test for storage_class dynamic subsystem storage_class is now a valid dynamic config subsystem, so the "requires object layer" test should expect "storage layer not initialized" instead of "unsupported dynamic config subsystem". Co-authored-by: hehutu <heihutu@gmail.com> * fix(config): publish storage_class runtime config on dynamic reload Change GLOBAL_STORAGE_CLASS from OnceLock to RwLock so runtime updates are possible. apply_storage_class_runtime_config now actually publishes the parsed config via set_global_storage_class instead of dropping it. Addresses review feedback: storage_class was marked as dynamically applied but the parsed result was discarded, so mc admin config set returned config_applied=true while the runtime kept using stale parity settings until restart. Co-authored-by: hehutu <heihutu@gmail.com> * fix(admin): harden config init, history ordering, and env redaction - Change GLOBAL_SERVER_CONFIG from RwLock<Config> to RwLock<Option<Config>> initialized with None, preserving "not initialized" detection via None - Move save_server_config_history before save_server_config_to_store in SetConfigKVHandler, DelConfigKVHandler, and SetConfigHandler so a restore point exists before mutations are persisted - Redact sensitive env override values with *redacted* instead of silently omitting the line, improving admin visibility - Add code comment explaining VolumeNotFound removal rationale in list_merged for listing paths Co-authored-by: hehutu <heihutu@gmail.com> * fix(config): keep in-memory config in sync after set/restore/reload GLOBAL_SERVER_CONFIG was a OnceLock set once at startup and never updated. After mc admin config set writes to the store, any fallback to get_global_server_config() returned stale init-time data. Similarly, reload_runtime_config_snapshot read from the store but discarded the result. - Replace OnceLock with RwLock for GLOBAL_SERVER_CONFIG and GLOBAL_STORAGE_CLASS so they can be updated at runtime - Add set_global_server_config / set_global_storage_class setters - Call set_global_server_config after every config save (set-kv, del-kv, set-config, restore-history) - Re-apply dynamic subsystems (storage_class, audit_webhook, audit_mqtt) and signal peers in reload_runtime_config_snapshot and full-config operations - Fix render_selected_config scope boundary check: track per-scope line count instead of checking global lines.is_empty() - Include STORAGE_CLASS_SUB_SYS in is_dynamic_config_subsystem so apply_storage_class_runtime_config is reachable Co-authored-by: hehutu <heihutu@gmail.com> * fix(storageclass): use CLASS_RRS key in lookup_config for RRS parity lookup_config used kvs.get(RRS) where RRS="REDUCED_REDUNDANCY", but the admin config path writes the key as CLASS_RRS="rrs". This caused RRS values to never be read back, always falling back to default parity. - Changed kvs.get(RRS) to kvs.get(CLASS_RRS) in lookup_config - Added regression tests verifying RRS read/write consistency Co-authored-by: hehutu <heihutu@gmail.com> * fix(config): add peer-side logging and don't swallow apply errors - Add tracing::warn! in reload_dynamic_config_runtime_state and reload_runtime_config_snapshot when config read or subsystem apply fails, so on-host diagnostics show which signal failed and why - Change `let _ = apply_dynamic_config_for_subsystem(...)` to `if let Err(err) = ... { warn!(...) }` in reload_runtime_config_snapshot so per-subsystem failures are logged instead of silently swallowed - Remove weak test global_server_config_returns_none_before_init that had no meaningful assertion due to shared global state Co-authored-by: hehutu <heihutu@gmail.com> * style: apply rustfmt to config and storageclass tests Co-authored-by: hehutu <heihutu@gmail.com> --------- Co-authored-by: weisd <im@weisd.in> Co-authored-by: hehutu <heihutu@gmail.com>
This commit is contained in:
@@ -3043,6 +3043,51 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn ecstore_new_succeeds_on_fresh_local_volumes() {
|
||||
let test_base_dir = format!("/tmp/rustfs_ecstore_empty_boot_{}", Uuid::new_v4());
|
||||
let temp_dir = PathBuf::from(&test_base_dir);
|
||||
if temp_dir.exists() {
|
||||
fs::remove_dir_all(&temp_dir).await.ok();
|
||||
}
|
||||
fs::create_dir_all(&temp_dir).await.unwrap();
|
||||
|
||||
let disk_paths = vec![
|
||||
temp_dir.join("disk1"),
|
||||
temp_dir.join("disk2"),
|
||||
temp_dir.join("disk3"),
|
||||
temp_dir.join("disk4"),
|
||||
];
|
||||
|
||||
for disk_path in &disk_paths {
|
||||
fs::create_dir_all(disk_path).await.unwrap();
|
||||
}
|
||||
|
||||
let mut endpoints = Vec::new();
|
||||
for (i, disk_path) in disk_paths.iter().enumerate() {
|
||||
let mut endpoint = Endpoint::try_from(disk_path.to_str().unwrap()).unwrap();
|
||||
endpoint.set_pool_index(0);
|
||||
endpoint.set_set_index(0);
|
||||
endpoint.set_disk_index(i);
|
||||
endpoints.push(endpoint);
|
||||
}
|
||||
|
||||
let endpoint_pools = EndpointServerPools(vec![PoolEndpoints {
|
||||
legacy: false,
|
||||
set_count: 1,
|
||||
drives_per_set: 4,
|
||||
endpoints: Endpoints::from(endpoints),
|
||||
cmd_line: "fresh-boot-test".to_string(),
|
||||
platform: format!("OS: {} | Arch: {}", std::env::consts::OS, std::env::consts::ARCH),
|
||||
}]);
|
||||
|
||||
crate::store::init_local_disks(endpoint_pools.clone()).await.unwrap();
|
||||
|
||||
let ecstore = ECStore::new("127.0.0.1:0".parse().unwrap(), endpoint_pools, CancellationToken::new()).await;
|
||||
assert!(ecstore.is_ok(), "fresh local ECStore boot should succeed, got {ecstore:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn stale_multipart_cleanup_uses_default_expiry_without_lifecycle() {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::config::{Config, GLOBAL_STORAGE_CLASS, KVS, audit, notify, oidc, storageclass};
|
||||
use crate::config::{Config, KVS, audit, notify, oidc, set_global_storage_class, storageclass};
|
||||
use crate::disk::{MIGRATING_META_BUCKET, RUSTFS_META_BUCKET};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::global::is_first_cluster_node_local;
|
||||
@@ -1164,11 +1164,8 @@ async fn apply_dynamic_config_for_sub_sys<S: StorageAPI>(cfg: &mut Config, api:
|
||||
for (i, count) in set_drive_counts.iter().enumerate() {
|
||||
match storageclass::lookup_config(&kvs, *count) {
|
||||
Ok(res) => {
|
||||
if i == 0
|
||||
&& GLOBAL_STORAGE_CLASS.get().is_none()
|
||||
&& let Err(r) = GLOBAL_STORAGE_CLASS.set(res)
|
||||
{
|
||||
error!("GLOBAL_STORAGE_CLASS.set failed {:?}", r);
|
||||
if i == 0 {
|
||||
set_global_storage_class(res);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -2638,4 +2635,87 @@ mod tests {
|
||||
assert_eq!(object_info.bucket, crate::disk::RUSTFS_META_BUCKET);
|
||||
assert_eq!(object_info.name, "config/test.json");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_decode_roundtrip_preserves_storage_class() {
|
||||
use crate::config::STORAGE_CLASS_SUB_SYS;
|
||||
|
||||
// Create a config with custom storage class values
|
||||
let mut cfg = Config::new();
|
||||
let kvs = storage_class_kvs_mut(&mut cfg);
|
||||
kvs.insert("standard".to_string(), "EC:4".to_string());
|
||||
kvs.insert("rrs".to_string(), "EC:2".to_string());
|
||||
kvs.insert("optimize".to_string(), "availability".to_string());
|
||||
|
||||
// Encode to external format (what save_server_config produces)
|
||||
let encoded = encode_server_config_blob(&cfg, None).expect("encode should succeed");
|
||||
|
||||
// Verify the encoded data uses "storageclass" (no underscore)
|
||||
let v: serde_json::Value = serde_json::from_slice(&encoded).expect("should be valid json");
|
||||
assert!(v.get("storageclass").is_some(), "encoded should have 'storageclass' key");
|
||||
assert!(v.get("storage_class").is_none(), "encoded should NOT have 'storage_class' key");
|
||||
|
||||
// Decode back (what load_server_config_from_store produces)
|
||||
let decoded = decode_server_config_blob(&encoded).expect("decode should succeed");
|
||||
|
||||
// Verify the decoded config has "storage_class" (with underscore) subsystem
|
||||
let kvs = decoded
|
||||
.get_value(STORAGE_CLASS_SUB_SYS, crate::config::DEFAULT_DELIMITER)
|
||||
.expect("decoded config should have storage_class subsystem");
|
||||
assert_eq!(kvs.get("standard"), "EC:4", "standard should be EC:4");
|
||||
assert_eq!(kvs.get("rrs"), "EC:2", "rrs should be EC:2");
|
||||
assert_eq!(kvs.get("optimize"), "availability", "optimize should be availability");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_then_load_preserves_storage_class_values() {
|
||||
use crate::config::STORAGE_CLASS_SUB_SYS;
|
||||
|
||||
// Step 1: Start with a fresh config (simulates initial server state)
|
||||
let mut cfg = Config::new();
|
||||
|
||||
// Step 2: Apply set directives (simulates "mc admin config set storage_class standard=EC:4 rrs=EC:2")
|
||||
let kvs = cfg
|
||||
.0
|
||||
.entry(STORAGE_CLASS_SUB_SYS.to_string())
|
||||
.or_default()
|
||||
.entry(DEFAULT_DELIMITER.to_string())
|
||||
.or_default();
|
||||
kvs.insert("standard".to_string(), "EC:4".to_string());
|
||||
kvs.insert("rrs".to_string(), "EC:2".to_string());
|
||||
cfg.set_defaults();
|
||||
|
||||
// Step 3: Save (encode to external format)
|
||||
let encoded = encode_server_config_blob(&cfg, None).expect("encode should succeed");
|
||||
|
||||
// Step 4: Load (decode from external format)
|
||||
let loaded = decode_server_config_blob(&encoded).expect("decode should succeed");
|
||||
|
||||
// Step 5: Verify the values are preserved
|
||||
let loaded_kvs = loaded
|
||||
.get_value(STORAGE_CLASS_SUB_SYS, DEFAULT_DELIMITER)
|
||||
.expect("should have storage_class");
|
||||
assert_eq!(loaded_kvs.get("standard"), "EC:4");
|
||||
assert_eq!(loaded_kvs.get("rrs"), "EC:2");
|
||||
|
||||
// Step 6: Verify the subsystem is accessible by name (what render_selected_config does)
|
||||
let targets = loaded
|
||||
.0
|
||||
.get(STORAGE_CLASS_SUB_SYS)
|
||||
.expect("storage_class subsystem should exist");
|
||||
let target_kvs = targets.get(DEFAULT_DELIMITER).expect("default target should exist");
|
||||
assert_eq!(target_kvs.get("standard"), "EC:4");
|
||||
assert_eq!(target_kvs.get("rrs"), "EC:2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_unmarshal_fails_on_external_format() {
|
||||
// This verifies that the external "storageclass" format cannot be
|
||||
// mistakenly parsed by Config::unmarshal (which expects internal format).
|
||||
// If unmarshal succeeds, the config would have "storageclass" instead of
|
||||
// "storage_class", causing render_selected_config to fail.
|
||||
let external_json = r#"{"version":"33","storageclass":{"standard":"EC:4","rrs":"EC:2"}}"#;
|
||||
let result = Config::unmarshal(external_json.as_bytes());
|
||||
assert!(result.is_err(), "Config::unmarshal should fail on external format, but got: {:?}", result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,11 +37,12 @@ use rustfs_config::oidc::IDENTITY_OPENID_SUB_SYS;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::sync::{Arc, OnceLock, RwLock};
|
||||
|
||||
pub static GLOBAL_STORAGE_CLASS: LazyLock<OnceLock<storageclass::Config>> = LazyLock::new(OnceLock::new);
|
||||
pub static GLOBAL_STORAGE_CLASS: LazyLock<RwLock<storageclass::Config>> =
|
||||
LazyLock::new(|| RwLock::new(storageclass::Config::default()));
|
||||
pub static DEFAULT_KVS: LazyLock<OnceLock<HashMap<String, KVS>>> = LazyLock::new(OnceLock::new);
|
||||
pub static GLOBAL_SERVER_CONFIG: LazyLock<OnceLock<Config>> = LazyLock::new(OnceLock::new);
|
||||
pub static GLOBAL_SERVER_CONFIG: LazyLock<RwLock<Option<Config>>> = LazyLock::new(|| RwLock::new(None));
|
||||
pub static GLOBAL_CONFIG_SYS: LazyLock<ConfigSys> = LazyLock::new(ConfigSys::new);
|
||||
|
||||
pub static RUSTFS_CONFIG_PREFIX: &str = "config";
|
||||
@@ -63,14 +64,30 @@ impl ConfigSys {
|
||||
|
||||
lookup_configs(&mut cfg, api).await;
|
||||
|
||||
let _ = GLOBAL_SERVER_CONFIG.set(cfg);
|
||||
set_global_server_config(cfg);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_global_server_config() -> Option<Config> {
|
||||
GLOBAL_SERVER_CONFIG.get().cloned()
|
||||
GLOBAL_SERVER_CONFIG.read().ok().and_then(|guard| (*guard).clone())
|
||||
}
|
||||
|
||||
pub fn set_global_server_config(cfg: Config) {
|
||||
if let Ok(mut guard) = GLOBAL_SERVER_CONFIG.write() {
|
||||
*guard = Some(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_global_storage_class() -> Option<storageclass::Config> {
|
||||
GLOBAL_STORAGE_CLASS.read().ok().map(|guard| (*guard).clone())
|
||||
}
|
||||
|
||||
pub fn set_global_storage_class(cfg: storageclass::Config) {
|
||||
if let Ok(mut guard) = GLOBAL_STORAGE_CLASS.write() {
|
||||
*guard = cfg;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init_global_config_sys(api: Arc<ECStore>) -> Result<()> {
|
||||
@@ -262,3 +279,25 @@ pub fn init() {
|
||||
// Register all default configurations
|
||||
register_default_kvs(kvs)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn global_server_config_set_and_get_roundtrip() {
|
||||
init();
|
||||
let mut cfg = Config::new();
|
||||
let mut kvs = KVS::new();
|
||||
kvs.insert("standard".to_string(), "EC:4".to_string());
|
||||
cfg.0
|
||||
.insert(STORAGE_CLASS_SUB_SYS.to_string(), HashMap::from([("_".to_string(), kvs)]));
|
||||
|
||||
set_global_server_config(cfg.clone());
|
||||
let loaded = get_global_server_config().expect("global config should be set");
|
||||
let sc_kvs = loaded
|
||||
.get_value(STORAGE_CLASS_SUB_SYS, "_")
|
||||
.expect("storage_class should exist");
|
||||
assert_eq!(sc_kvs.get("standard"), "EC:4");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,13 +101,13 @@ pub static DEFAULT_KVS: LazyLock<KVS> = LazyLock::new(|| {
|
||||
});
|
||||
|
||||
// StorageClass - holds storage class information
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct StorageClass {
|
||||
parity: usize,
|
||||
}
|
||||
|
||||
// Config storage class configuration
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct Config {
|
||||
standard: StorageClass,
|
||||
rrs: StorageClass,
|
||||
@@ -205,7 +205,7 @@ pub fn lookup_config(kvs: &KVS, set_drive_count: usize) -> Result<Config> {
|
||||
if let Ok(ssc_str) = env::var(RRS_ENV) {
|
||||
ssc_str
|
||||
} else {
|
||||
kvs.get(RRS)
|
||||
kvs.get(CLASS_RRS)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -336,3 +336,36 @@ pub fn validate_parity_inner(ss_parity: usize, rrs_parity: usize, set_drive_coun
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn lookup_config_reads_rrs_from_class_rrs_key() {
|
||||
// Regression: kvs.get(RRS) used RRS="REDUCED_REDUNDANCY" instead of
|
||||
// CLASS_RRS="rrs", so admin-written RRS values were never read back.
|
||||
let mut kvs = KVS::new();
|
||||
kvs.insert(CLASS_STANDARD.to_string(), "EC:4".to_string());
|
||||
kvs.insert(CLASS_RRS.to_string(), "EC:2".to_string());
|
||||
|
||||
let cfg = lookup_config(&kvs, 8).expect("lookup should succeed");
|
||||
assert_eq!(cfg.standard.parity, 4, "standard parity should be 4");
|
||||
assert_eq!(cfg.rrs.parity, 2, "rrs parity should be 2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_config_ignores_redundancy_key_name() {
|
||||
// Ensure the old key name "REDUCED_REDUNDANCY" is NOT read.
|
||||
let mut kvs = KVS::new();
|
||||
kvs.insert(CLASS_STANDARD.to_string(), "EC:4".to_string());
|
||||
kvs.insert(RRS.to_string(), "EC:2".to_string());
|
||||
|
||||
let cfg = lookup_config(&kvs, 8).expect("lookup should succeed");
|
||||
assert_eq!(cfg.standard.parity, 4);
|
||||
assert_eq!(
|
||||
cfg.rrs.parity, DEFAULT_RRS_PARITY,
|
||||
"rrs should fall back to default when CLASS_RRS key is absent"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,65 @@ impl NotificationSys {
|
||||
join_all(futures).await
|
||||
}
|
||||
|
||||
pub async fn reload_dynamic_config(&self, sub_sys: &str) -> Vec<NotificationPeerErr> {
|
||||
let mut futures = Vec::with_capacity(self.peer_clients.len());
|
||||
for client in self.peer_clients.iter() {
|
||||
let sub_sys = sub_sys.to_string();
|
||||
futures.push(async move {
|
||||
if let Some(client) = client {
|
||||
match client
|
||||
.signal_service(crate::rpc::SERVICE_SIGNAL_RELOAD_DYNAMIC, &sub_sys, false, SystemTime::UNIX_EPOCH)
|
||||
.await
|
||||
{
|
||||
Ok(_) => NotificationPeerErr {
|
||||
host: client.host.to_string(),
|
||||
err: None,
|
||||
},
|
||||
Err(e) => NotificationPeerErr {
|
||||
host: client.host.to_string(),
|
||||
err: Some(e),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
NotificationPeerErr {
|
||||
host: "".to_string(),
|
||||
err: Some(Error::other("peer is not reachable")),
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
join_all(futures).await
|
||||
}
|
||||
|
||||
pub async fn refresh_config_snapshot(&self) -> Vec<NotificationPeerErr> {
|
||||
let mut futures = Vec::with_capacity(self.peer_clients.len());
|
||||
for client in self.peer_clients.iter() {
|
||||
futures.push(async move {
|
||||
if let Some(client) = client {
|
||||
match client
|
||||
.signal_service(crate::rpc::SERVICE_SIGNAL_REFRESH_CONFIG, "", false, SystemTime::UNIX_EPOCH)
|
||||
.await
|
||||
{
|
||||
Ok(_) => NotificationPeerErr {
|
||||
host: client.host.to_string(),
|
||||
err: None,
|
||||
},
|
||||
Err(e) => NotificationPeerErr {
|
||||
host: client.host.to_string(),
|
||||
err: Some(e),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
NotificationPeerErr {
|
||||
host: "".to_string(),
|
||||
err: Some(Error::other("peer is not reachable")),
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
join_all(futures).await
|
||||
}
|
||||
|
||||
pub async fn storage_info<S: StorageAPI>(&self, api: &S) -> rustfs_madmin::StorageInfo {
|
||||
let mut futures = Vec::with_capacity(self.peer_clients.len());
|
||||
let endpoints = get_global_endpoints();
|
||||
|
||||
@@ -29,7 +29,10 @@ pub use internode_data_transport::{
|
||||
InternodeDataTransport, InternodeDataTransportCapabilities, ReadStreamRequest, TcpHttpInternodeDataTransport,
|
||||
WalkDirStreamRequest, WriteStreamRequest, build_internode_data_transport, build_internode_data_transport_from_env,
|
||||
};
|
||||
pub use peer_rest_client::PeerRestClient;
|
||||
pub use peer_rest_client::{
|
||||
PEER_RESTDRY_RUN, PEER_RESTSIGNAL, PEER_RESTSUB_SYS, PeerRestClient, SERVICE_SIGNAL_REFRESH_CONFIG,
|
||||
SERVICE_SIGNAL_RELOAD_DYNAMIC,
|
||||
};
|
||||
pub use peer_s3_client::{LocalPeerS3Client, PeerS3Client, RemotePeerS3Client, S3PeerSys};
|
||||
pub use remote_disk::RemoteDisk;
|
||||
pub use remote_locker::RemoteClient;
|
||||
|
||||
@@ -57,6 +57,8 @@ use tracing::warn;
|
||||
pub const PEER_RESTSIGNAL: &str = "signal";
|
||||
pub const PEER_RESTSUB_SYS: &str = "sub-sys";
|
||||
pub const PEER_RESTDRY_RUN: &str = "dry-run";
|
||||
pub const SERVICE_SIGNAL_REFRESH_CONFIG: u64 = 1;
|
||||
pub const SERVICE_SIGNAL_RELOAD_DYNAMIC: u64 = 2;
|
||||
const PEER_REST_RECOVERY_MAX_ATTEMPTS: u32 = 60;
|
||||
const PEER_REST_RECOVERY_MAX_BACKOFF: Duration = Duration::from_secs(30);
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ use crate::{
|
||||
LifecycleOps, gen_transition_objname, get_transitioned_object_reader, put_restore_opts,
|
||||
},
|
||||
cache_value::metacache_set::{ListPathRawOptions, list_path_raw},
|
||||
config::{GLOBAL_STORAGE_CLASS, storageclass},
|
||||
config::{get_global_storage_class, storageclass},
|
||||
disk::{
|
||||
CheckPartsResp, DeleteOptions, DiskAPI, DiskInfo, DiskInfoOptions, DiskOption, DiskStore, FileInfoVersions,
|
||||
RUSTFS_META_BUCKET, RUSTFS_META_MULTIPART_BUCKET, RUSTFS_META_TMP_BUCKET, ReadMultipleReq, ReadMultipleResp, ReadOptions,
|
||||
@@ -282,8 +282,7 @@ fn build_tiered_decommission_file_info(
|
||||
default_parity_count: usize,
|
||||
storage_class: Option<&str>,
|
||||
) -> (FileInfo, usize) {
|
||||
let parity_drives = GLOBAL_STORAGE_CLASS
|
||||
.get()
|
||||
let parity_drives = get_global_storage_class()
|
||||
.and_then(|sc| sc.get_parity_for_sc(storage_class.unwrap_or_default()))
|
||||
.unwrap_or(default_parity_count);
|
||||
let data_drives = disk_count - parity_drives;
|
||||
@@ -783,7 +782,7 @@ impl ObjectIO for SetDisks {
|
||||
}
|
||||
}
|
||||
let sc_parity_drives = {
|
||||
if let Some(sc) = GLOBAL_STORAGE_CLASS.get() {
|
||||
if let Some(sc) = get_global_storage_class() {
|
||||
sc.get_parity_for_sc(user_defined.get(AMZ_STORAGE_CLASS).cloned().unwrap_or_default().as_str())
|
||||
} else {
|
||||
None
|
||||
@@ -837,7 +836,7 @@ impl ObjectIO for SetDisks {
|
||||
let erasure = erasure_coding::Erasure::new(fi.erasure.data_blocks, fi.erasure.parity_blocks, fi.erasure.block_size);
|
||||
|
||||
let is_inline_buffer = {
|
||||
if let Some(sc) = GLOBAL_STORAGE_CLASS.get() {
|
||||
if let Some(sc) = get_global_storage_class() {
|
||||
sc.should_inline(erasure.shard_file_size(data.size()), opts.versioned)
|
||||
} else {
|
||||
false
|
||||
@@ -3108,7 +3107,7 @@ impl MultipartOperations for SetDisks {
|
||||
}
|
||||
|
||||
let sc_parity_drives = {
|
||||
if let Some(sc) = GLOBAL_STORAGE_CLASS.get() {
|
||||
if let Some(sc) = get_global_storage_class() {
|
||||
sc.get_parity_for_sc(user_defined.get(AMZ_STORAGE_CLASS).cloned().unwrap_or_default().as_str())
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -417,7 +417,7 @@ impl SetDisks {
|
||||
}
|
||||
|
||||
let is_inline_buffer = {
|
||||
if let Some(sc) = GLOBAL_STORAGE_CLASS.get() {
|
||||
if let Some(sc) = get_global_storage_class() {
|
||||
sc.should_inline(erasure.shard_file_size(latest_meta.size), false)
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -31,7 +31,6 @@ use crate::bucket::utils::check_object_args;
|
||||
use crate::bucket::utils::check_put_object_args;
|
||||
use crate::bucket::utils::check_put_object_part_args;
|
||||
use crate::bucket::utils::{check_valid_bucket_name, check_valid_bucket_name_strict, is_meta_bucketname};
|
||||
use crate::config::GLOBAL_STORAGE_CLASS;
|
||||
use crate::config::storageclass;
|
||||
use crate::disk::endpoint::{Endpoint, EndpointType};
|
||||
use crate::disk::{DiskAPI, DiskInfo, DiskInfoOptions};
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
use super::*;
|
||||
use crate::config::get_global_storage_class;
|
||||
|
||||
struct LatestObjectInfoCandidate {
|
||||
info: Option<ObjectInfo>,
|
||||
@@ -605,7 +606,7 @@ impl ECStore {
|
||||
#[instrument(skip(self))]
|
||||
pub(super) async fn handle_backend_info(&self) -> rustfs_madmin::BackendInfo {
|
||||
let (standard_sc_parity, rr_sc_parity) = {
|
||||
if let Some(sc) = GLOBAL_STORAGE_CLASS.get() {
|
||||
if let Some(sc) = get_global_storage_class() {
|
||||
let sc_parity = sc
|
||||
.get_parity_for_sc(storageclass::CLASS_STANDARD)
|
||||
.or(Some(self.pools[0].default_parity_count));
|
||||
|
||||
@@ -841,11 +841,13 @@ impl ECStore {
|
||||
}
|
||||
}
|
||||
|
||||
// All sets returned not-found — the listing is simply empty.
|
||||
// We intentionally do NOT distinguish VolumeNotFound here: during
|
||||
// listing, a missing volume on a set is equivalent to "no entries"
|
||||
// and must not surface as an error to the caller. The original
|
||||
// VolumeNotFound check caused spurious errors when a pool had not
|
||||
// yet created the bucket prefix on every set.
|
||||
if is_all_not_found(&errs) {
|
||||
if is_all_volume_not_found(&errs) {
|
||||
return Err(StorageError::VolumeNotFound);
|
||||
}
|
||||
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user