1use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::str::FromStr;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::Mutex;
8use std::time::{Duration, Instant};
9
10use crate::auth::AuthType;
11use crate::proto::grpc::file::WritePType;
12
13#[derive(Debug)]
17pub enum ConfigLoadError {
18 IoError { path: String, source: String },
20 ParseError { message: String },
22}
23
24impl std::fmt::Display for ConfigLoadError {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match self {
27 ConfigLoadError::IoError { path, source } => {
28 write!(f, "failed to read config file '{}': {}", path, source)
29 }
30 ConfigLoadError::ParseError { message } => {
31 write!(f, "failed to parse YAML config: {}", message)
32 }
33 }
34 }
35}
36
37impl std::error::Error for ConfigLoadError {}
38
39use std::collections::HashMap;
45
46#[derive(Debug, Default)]
48struct PropertiesMap {
49 props: HashMap<String, String>,
50}
51
52impl PropertiesMap {
53 fn parse(content: &str) -> Self {
61 let mut props = HashMap::new();
62 for line in content.lines() {
63 let trimmed = line.trim();
64 if trimmed.is_empty() || trimmed.starts_with('#') || trimmed.starts_with('!') {
65 continue;
66 }
67 let sep_pos = trimmed.find('=').or_else(|| trimmed.find(':'));
69 if let Some(pos) = sep_pos {
70 let key = trimmed[..pos].trim().to_string();
71 let value = trimmed[pos + 1..].trim().to_string();
72 if !key.is_empty() {
73 props.insert(key, value);
74 }
75 }
76 }
77 PropertiesMap { props }
78 }
79
80 fn get(&self, key: &str) -> Option<&str> {
82 self.props.get(key).map(|s| s.as_str())
83 }
84
85 fn get_parsed<T: FromStr>(&self, key: &str) -> Option<T> {
87 self.get(key).and_then(|v| v.parse::<T>().ok())
88 }
89
90 fn get_bool(&self, key: &str) -> Option<bool> {
92 self.get(key)
93 .and_then(|v| v.to_ascii_lowercase().parse::<bool>().ok())
94 }
95
96 fn get_list(&self, key: &str) -> Option<Vec<String>> {
98 self.get(key).map(|v| {
99 v.split(',')
100 .map(str::trim)
101 .filter(|s| !s.is_empty())
102 .map(String::from)
103 .collect()
104 })
105 }
106}
107
108fn parse_byte_size(s: &str) -> Result<u64, String> {
113 let s = s.trim();
114 let upper = s.to_uppercase();
115 let (multiplier, num_str) = if upper.ends_with("GB") {
116 (1024u64 * 1024 * 1024, &s[..s.len() - 2])
117 } else if upper.ends_with("MB") {
118 (1024 * 1024, &s[..s.len() - 2])
119 } else if upper.ends_with("KB") {
120 (1024, &s[..s.len() - 2])
121 } else {
122 (1, s)
123 };
124 num_str
125 .trim()
126 .parse::<u64>()
127 .map(|n| n * multiplier)
128 .map_err(|e| format!("invalid byte size '{}': {}", s, e))
129}
130
131impl PropertiesMap {
132 fn into_goosefs_config(self) -> GoosefsConfig {
134 let mut cfg = GoosefsConfig::default();
135
136 if let Some(addrs) = self.get_list("goosefs.master.rpc.addresses") {
138 if !addrs.is_empty() {
139 cfg.master_addr = addrs[0].clone();
140 if addrs.len() > 1 {
141 cfg.master_addrs = addrs;
142 }
143 }
144 } else if let Some(host) = self.get("goosefs.master.hostname") {
145 let port: u16 = self.get_parsed("goosefs.master.rpc.port").unwrap_or(9200);
146 cfg.master_addr = format!("{}:{}", host, port);
147 }
148
149 if let Some(addrs) = self.get_list("goosefs.config.manager.rpc.addresses") {
151 if !addrs.is_empty() {
152 cfg.config_manager_rpc_addresses = addrs;
153 }
154 }
155
156 if let Some(port) = self.get_parsed::<u16>("goosefs.config.rpc.port") {
158 cfg.config_rpc_port = port;
159 }
160
161 if let Some(at_str) = self.get("goosefs.security.authentication.type") {
163 if let Ok(at) = at_str.parse::<AuthType>() {
164 cfg.auth_type = at;
165 }
166 }
167
168 if let Some(enabled) = self.get_bool("goosefs.security.authorization.permission.enabled") {
170 cfg.authorization_permission_enabled = enabled;
171 }
172
173 if let Some(user) = self.get("goosefs.security.login.impersonation.username") {
175 if !user.is_empty() {
176 cfg.login_impersonation_username = user.to_string();
177 }
178 }
179
180 if let Some(user) = self.get("goosefs.security.login.username") {
182 if !user.is_empty() {
183 cfg.auth_username = user.to_string();
184 }
185 }
186
187 if let Some(enabled) = self.get_bool("goosefs.user.client.transparent_acceleration.enabled")
189 {
190 cfg.transparent_acceleration_enabled = enabled;
191 }
192
193 if let Some(enabled) =
195 self.get_bool("goosefs.user.client.transparent_acceleration.cosranger.enabled")
196 {
197 cfg.transparent_acceleration_cosranger_enabled = enabled;
198 }
199
200 if let Some(wt_str) = self.get("goosefs.user.file.writetype.default") {
202 if let Ok(wt) = wt_str.parse::<WriteType>() {
203 cfg.write_type = Some(wt.as_i32());
204 }
205 }
206
207 if let Some(bs_str) = self.get("goosefs.user.block.size.bytes.default") {
209 if let Ok(bs) = parse_byte_size(bs_str) {
210 if bs > 0 {
211 cfg.block_size = bs;
212 }
213 }
214 }
215
216 if let Some(cs_str) = self.get("goosefs.user.network.data.transfer.chunk.size") {
218 if let Ok(cs) = parse_byte_size(cs_str) {
219 if cs > 0 {
220 cfg.chunk_size = cs;
221 }
222 }
223 }
224
225 cfg
226 }
227}
228
229const PROPERTIES_FILENAME: &str = "goosefs-site.properties";
231
232pub fn discover_config_file() -> Option<std::path::PathBuf> {
244 use std::path::PathBuf;
245
246 if let Ok(p) = std::env::var(ENV_CONFIG_FILE) {
248 let pb = PathBuf::from(&p);
249 if pb.exists() {
250 return Some(pb);
251 }
252 }
253
254 if let Ok(conf_dir) = std::env::var(CONF_DIR) {
256 let p = PathBuf::from(&conf_dir).join(PROPERTIES_FILENAME);
257 if p.exists() {
258 return Some(p);
259 }
260 }
261
262 if let Ok(home) = std::env::var(ENV_HOME) {
264 let p = PathBuf::from(&home).join("conf").join(PROPERTIES_FILENAME);
265 if p.exists() {
266 return Some(p);
267 }
268 }
269
270 if let Some(home) = dirs_next_home() {
272 let p = home.join(".goosefs").join(PROPERTIES_FILENAME);
273 if p.exists() {
274 return Some(p);
275 }
276 }
277
278 let system = PathBuf::from("/etc/goosefs").join(PROPERTIES_FILENAME);
280 if system.exists() {
281 return Some(system);
282 }
283
284 None
285}
286
287fn dirs_next_home() -> Option<std::path::PathBuf> {
289 std::env::var("HOME")
290 .or_else(|_| std::env::var("USERPROFILE"))
291 .ok()
292 .map(std::path::PathBuf::from)
293}
294
295const DEFAULT_MASTER_PORT: u16 = 9200;
299#[allow(dead_code)]
301const DEFAULT_WORKER_PORT: u16 = 9203;
302const DEFAULT_BLOCK_SIZE: u64 = 64 * 1024 * 1024;
304const DEFAULT_CHUNK_SIZE: u64 = 1024 * 1024;
306const DEFAULT_CONNECT_TIMEOUT_MS: u64 = 30_000;
308const DEFAULT_REQUEST_TIMEOUT_MS: u64 = 300_000;
310const DEFAULT_MASTER_POLLING_TIMEOUT_MS: u64 = 30_000;
312
313const DEFAULT_AUTH_TIMEOUT_MS: u64 = 30_000;
315
316const DEFAULT_CONFIG_RPC_PORT: u16 = 9214;
318
319const DEFAULT_IMPERSONATION_USERNAME: &str = "_HDFS_USER_";
321#[allow(dead_code)]
323pub const IMPERSONATION_NONE: &str = "_NONE_";
324
325const DEFAULT_MASTER_INQUIRE_MAX_DURATION_MS: u64 = 120_000;
327const DEFAULT_MASTER_INQUIRE_INITIAL_SLEEP_MS: u64 = 50;
329const DEFAULT_MASTER_INQUIRE_MAX_SLEEP_MS: u64 = 3_000;
331
332const DEFAULT_CONFIG_EXPIRE_MS: u64 = 30_000;
334
335pub const STORAGE_OPT_MASTER_ADDR: &str = "goosefs_master_addr";
348
349pub const STORAGE_OPT_WRITE_TYPE: &str = "goosefs_write_type";
356
357pub const STORAGE_OPT_BLOCK_SIZE: &str = "goosefs_block_size";
361
362pub const STORAGE_OPT_CHUNK_SIZE: &str = "goosefs_chunk_size";
366
367pub const STORAGE_OPT_AUTH_TYPE: &str = "goosefs_auth_type";
373
374pub const STORAGE_OPT_AUTH_USERNAME: &str = "goosefs_auth_username";
378
379pub const CONF_DIR: &str = "goosefs.conf.dir";
384
385pub const ENV_CONFIG_FILE: &str = "GOOSEFS_CONFIG_FILE";
387
388pub const ENV_CONF_DIR: &str = "GOOSEFS_CONF_DIR";
392
393pub const ENV_HOME: &str = "GOOSEFS_HOME";
395
396pub const ENV_MASTER_ADDR: &str = "GOOSEFS_MASTER_ADDR";
398
399pub const ENV_WRITE_TYPE: &str = "GOOSEFS_WRITE_TYPE";
401
402pub const ENV_BLOCK_SIZE: &str = "GOOSEFS_BLOCK_SIZE";
404
405pub const ENV_CHUNK_SIZE: &str = "GOOSEFS_CHUNK_SIZE";
407
408pub const ENV_AUTH_TYPE: &str = "GOOSEFS_AUTH_TYPE";
410
411pub const ENV_AUTH_USERNAME: &str = "GOOSEFS_AUTH_USERNAME";
413
414pub const ENV_CONFIG_MANAGER_RPC_ADDRESSES: &str = "GOOSEFS_CONFIG_MANAGER_RPC_ADDRESSES";
416
417pub const ENV_CONFIG_RPC_PORT: &str = "GOOSEFS_CONFIG_RPC_PORT";
419
420pub const ENV_TRANSPARENT_ACCELERATION_ENABLED: &str = "GOOSEFS_TRANSPARENT_ACCELERATION_ENABLED";
422
423pub const ENV_TRANSPARENT_ACCELERATION_COSRANGER_ENABLED: &str =
425 "GOOSEFS_TRANSPARENT_ACCELERATION_COSRANGER_ENABLED";
426
427pub const ENV_AUTHORIZATION_PERMISSION_ENABLED: &str = "GOOSEFS_AUTHORIZATION_PERMISSION_ENABLED";
429
430pub const ENV_LOGIN_IMPERSONATION_USERNAME: &str = "GOOSEFS_LOGIN_IMPERSONATION_USERNAME";
432
433pub const STORAGE_OPT_CONFIG_MANAGER_RPC_ADDRESSES: &str = "goosefs_config_manager_rpc_addresses";
435
436pub const STORAGE_OPT_CONFIG_RPC_PORT: &str = "goosefs_config_rpc_port";
438
439pub const STORAGE_OPT_TRANSPARENT_ACCELERATION_ENABLED: &str =
441 "goosefs_transparent_acceleration_enabled";
442
443pub const STORAGE_OPT_TRANSPARENT_ACCELERATION_COSRANGER_ENABLED: &str =
445 "goosefs_transparent_acceleration_cosranger_enabled";
446
447pub const STORAGE_OPT_AUTHORIZATION_PERMISSION_ENABLED: &str =
449 "goosefs_authorization_permission_enabled";
450
451pub const STORAGE_OPT_LOGIN_IMPERSONATION_USERNAME: &str = "goosefs_login_impersonation_username";
453
454#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
492pub enum WriteType {
493 MustCache,
495 TryCache,
497 CacheThrough,
499 Through,
501 AsyncThrough,
503}
504
505impl WriteType {
506 pub const ALL: &'static [WriteType] = &[
508 WriteType::MustCache,
509 WriteType::TryCache,
510 WriteType::CacheThrough,
511 WriteType::Through,
512 WriteType::AsyncThrough,
513 ];
514
515 pub fn as_str(&self) -> &'static str {
519 match self {
520 WriteType::MustCache => "must_cache",
521 WriteType::TryCache => "try_cache",
522 WriteType::CacheThrough => "cache_through",
523 WriteType::Through => "through",
524 WriteType::AsyncThrough => "async_through",
525 }
526 }
527
528 pub fn as_i32(&self) -> i32 {
530 WritePType::from(*self) as i32
531 }
532}
533
534impl fmt::Display for WriteType {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 f.write_str(self.as_str())
537 }
538}
539
540impl FromStr for WriteType {
544 type Err = String;
545
546 fn from_str(s: &str) -> Result<Self, Self::Err> {
547 match s.to_ascii_lowercase().as_str() {
548 "must_cache" => Ok(WriteType::MustCache),
549 "try_cache" => Ok(WriteType::TryCache),
550 "cache_through" => Ok(WriteType::CacheThrough),
551 "through" => Ok(WriteType::Through),
552 "async_through" => Ok(WriteType::AsyncThrough),
553 _ => Err(format!(
554 "unknown write type '{}'. Expected one of: {}",
555 s,
556 WriteType::ALL
557 .iter()
558 .map(|wt| wt.as_str())
559 .collect::<Vec<_>>()
560 .join(", ")
561 )),
562 }
563 }
564}
565
566impl From<WriteType> for WritePType {
568 fn from(wt: WriteType) -> Self {
569 match wt {
570 WriteType::MustCache => WritePType::MustCache,
571 WriteType::TryCache => WritePType::TryCache,
572 WriteType::CacheThrough => WritePType::CacheThrough,
573 WriteType::Through => WritePType::Through,
574 WriteType::AsyncThrough => WritePType::AsyncThrough,
575 }
576 }
577}
578
579impl WriteType {
583 pub fn try_from_proto(pt: WritePType) -> Result<Self, String> {
584 match pt {
585 WritePType::MustCache => Ok(WriteType::MustCache),
586 WritePType::TryCache => Ok(WriteType::TryCache),
587 WritePType::CacheThrough => Ok(WriteType::CacheThrough),
588 WritePType::Through => Ok(WriteType::Through),
589 WritePType::AsyncThrough => Ok(WriteType::AsyncThrough),
590 other => Err(format!(
591 "cannot convert WritePType::{:?} to WriteType",
592 other
593 )),
594 }
595 }
596}
597
598impl From<WritePType> for WriteType {
600 fn from(pt: WritePType) -> Self {
601 Self::try_from_proto(pt).expect("cannot convert Unspecified/None WritePType to WriteType")
602 }
603}
604
605#[derive(Debug, Clone, Serialize, Deserialize)]
607pub struct GoosefsConfig {
608 pub master_addr: String,
614
615 #[serde(default)]
624 pub master_addrs: Vec<String>,
625
626 pub block_size: u64,
628
629 pub chunk_size: u64,
631
632 pub connect_timeout: Duration,
634
635 pub request_timeout: Duration,
637
638 pub use_vpc_mapping: bool,
640
641 pub root: String,
643
644 pub write_type: Option<i32>,
657
658 #[serde(default = "default_master_inquire_max_duration")]
661 pub master_inquire_retry_max_duration: Duration,
662
663 #[serde(default = "default_master_inquire_initial_sleep")]
665 pub master_inquire_initial_sleep: Duration,
666
667 #[serde(default = "default_master_inquire_max_sleep")]
669 pub master_inquire_max_sleep: Duration,
670
671 #[serde(default = "default_master_polling_timeout")]
677 pub master_polling_timeout: Duration,
678
679 #[serde(default)]
691 pub auth_type: AuthType,
692
693 #[serde(default = "default_auth_username")]
698 pub auth_username: String,
699
700 #[serde(default = "default_auth_timeout")]
705 pub auth_timeout: Duration,
706
707 #[serde(default)]
713 pub config_manager_rpc_addresses: Vec<String>,
714
715 #[serde(default = "default_config_rpc_port")]
719 pub config_rpc_port: u16,
720
721 #[serde(default = "default_transparent_acceleration_enabled")]
726 pub transparent_acceleration_enabled: bool,
727
728 #[serde(default)]
732 pub transparent_acceleration_cosranger_enabled: bool,
733
734 #[serde(default)]
739 pub authorization_permission_enabled: bool,
740
741 #[serde(default = "default_login_impersonation_username")]
748 pub login_impersonation_username: String,
749}
750
751fn default_master_inquire_max_duration() -> Duration {
752 Duration::from_millis(DEFAULT_MASTER_INQUIRE_MAX_DURATION_MS)
753}
754fn default_master_inquire_initial_sleep() -> Duration {
755 Duration::from_millis(DEFAULT_MASTER_INQUIRE_INITIAL_SLEEP_MS)
756}
757fn default_master_inquire_max_sleep() -> Duration {
758 Duration::from_millis(DEFAULT_MASTER_INQUIRE_MAX_SLEEP_MS)
759}
760fn default_master_polling_timeout() -> Duration {
761 Duration::from_millis(DEFAULT_MASTER_POLLING_TIMEOUT_MS)
762}
763fn default_auth_username() -> String {
764 std::env::var("USER")
765 .or_else(|_| std::env::var("USERNAME"))
766 .unwrap_or_else(|_| "unknown".to_string())
767}
768fn default_auth_timeout() -> Duration {
769 Duration::from_millis(DEFAULT_AUTH_TIMEOUT_MS)
770}
771fn default_config_rpc_port() -> u16 {
772 DEFAULT_CONFIG_RPC_PORT
773}
774fn default_transparent_acceleration_enabled() -> bool {
775 true
776}
777fn default_login_impersonation_username() -> String {
778 DEFAULT_IMPERSONATION_USERNAME.to_string()
779}
780
781impl Default for GoosefsConfig {
782 fn default() -> Self {
783 Self {
784 master_addr: format!("127.0.0.1:{}", DEFAULT_MASTER_PORT),
785 master_addrs: Vec::new(),
786 block_size: DEFAULT_BLOCK_SIZE,
787 chunk_size: DEFAULT_CHUNK_SIZE,
788 connect_timeout: Duration::from_millis(DEFAULT_CONNECT_TIMEOUT_MS),
789 request_timeout: Duration::from_millis(DEFAULT_REQUEST_TIMEOUT_MS),
790 use_vpc_mapping: false,
791 root: String::new(),
792 write_type: None,
793 master_inquire_retry_max_duration: default_master_inquire_max_duration(),
794 master_inquire_initial_sleep: default_master_inquire_initial_sleep(),
795 master_inquire_max_sleep: default_master_inquire_max_sleep(),
796 master_polling_timeout: default_master_polling_timeout(),
797 auth_type: AuthType::default(),
798 auth_username: default_auth_username(),
799 auth_timeout: default_auth_timeout(),
800 config_manager_rpc_addresses: Vec::new(),
801 config_rpc_port: default_config_rpc_port(),
802 transparent_acceleration_enabled: default_transparent_acceleration_enabled(),
803 transparent_acceleration_cosranger_enabled: false,
804 authorization_permission_enabled: false,
805 login_impersonation_username: default_login_impersonation_username(),
806 }
807 }
808}
809
810impl GoosefsConfig {
811 pub fn new(master_addr: impl Into<String>) -> Self {
813 Self {
814 master_addr: master_addr.into(),
815 ..Default::default()
816 }
817 }
818
819 pub fn new_ha(addrs: Vec<String>) -> Self {
827 assert!(!addrs.is_empty(), "master addresses must not be empty");
828 Self {
829 master_addr: addrs[0].clone(),
830 master_addrs: addrs,
831 ..Default::default()
832 }
833 }
834
835 pub fn from_addresses(addrs: Vec<String>) -> Self {
844 assert!(!addrs.is_empty(), "master addresses must not be empty");
845 if addrs.len() == 1 {
846 Self::new(&addrs[0])
847 } else {
848 Self::new_ha(addrs)
849 }
850 }
851
852 pub fn master_addresses(&self) -> Vec<String> {
857 if self.master_addrs.is_empty() {
858 vec![self.master_addr.clone()]
859 } else {
860 self.master_addrs.clone()
861 }
862 }
863
864 pub fn is_multi_master(&self) -> bool {
866 self.master_addrs.len() > 1
867 }
868
869 pub fn full_path(&self, path: &str) -> String {
871 if self.root.is_empty() {
872 path.to_string()
873 } else {
874 let root = self.root.trim_end_matches('/');
875 let path = path.trim_start_matches('/');
876 format!("{}/{}", root, path)
877 }
878 }
879
880 pub fn master_endpoint(&self) -> String {
882 format!("http://{}", self.master_addr)
883 }
884
885 pub fn worker_endpoint(&self, host: &str, rpc_port: i32) -> String {
887 if self.use_vpc_mapping {
888 format!("http://{}:{}", host, rpc_port)
890 } else {
891 format!("http://{}:{}", host, rpc_port)
892 }
893 }
894
895 pub fn with_auth_type(mut self, auth_type: AuthType) -> Self {
906 self.auth_type = auth_type;
907 self
908 }
909
910 pub fn with_auth_type_str(self, auth_type: &str) -> Result<Self, String> {
914 let at: AuthType = auth_type.parse()?;
915 Ok(self.with_auth_type(at))
916 }
917
918 pub fn with_auth_username(mut self, username: impl Into<String>) -> Self {
920 self.auth_username = username.into();
921 self
922 }
923
924 pub fn with_write_type(mut self, wt: WritePType) -> Self {
935 self.write_type = Some(wt as i32);
936 self
937 }
938
939 pub fn with_write_type_enum(mut self, wt: WriteType) -> Self {
949 self.write_type = Some(wt.as_i32());
950 self
951 }
952
953 pub fn with_write_type_str(self, wt: &str) -> Result<Self, String> {
967 let write_type: WriteType = wt.parse()?;
968 Ok(self.with_write_type_enum(write_type))
969 }
970
971 pub fn get_write_type(&self) -> Option<WritePType> {
975 self.write_type.and_then(|v| match v {
976 0 => Some(WritePType::UnspecifiedWriteType),
977 1 => Some(WritePType::MustCache),
978 2 => Some(WritePType::TryCache),
979 3 => Some(WritePType::CacheThrough),
980 4 => Some(WritePType::Through),
981 5 => Some(WritePType::AsyncThrough),
982 6 => Some(WritePType::None),
983 _ => Option::None,
984 })
985 }
986
987 pub fn from_env() -> Self {
1011 Self::default().apply_env()
1012 }
1013
1014 pub fn apply_env(mut self) -> Self {
1019 use std::env;
1020
1021 if let Ok(addr) = env::var(ENV_MASTER_ADDR) {
1023 let addrs: Vec<String> = addr
1024 .split(',')
1025 .map(str::trim)
1026 .filter(|s| !s.is_empty())
1027 .map(String::from)
1028 .collect();
1029 if !addrs.is_empty() {
1030 self.master_addr = addrs[0].clone();
1031 if addrs.len() > 1 {
1032 self.master_addrs = addrs;
1033 } else {
1034 self.master_addrs = Vec::new();
1035 }
1036 }
1037 }
1038
1039 if let Ok(wt_str) = env::var(ENV_WRITE_TYPE) {
1041 if let Ok(wt) = wt_str.parse::<WriteType>() {
1042 self.write_type = Some(wt.as_i32());
1043 }
1044 }
1045
1046 if let Ok(bs_str) = env::var(ENV_BLOCK_SIZE) {
1048 if let Ok(bs) = bs_str.parse::<u64>() {
1049 self.block_size = bs;
1050 }
1051 }
1052
1053 if let Ok(cs_str) = env::var(ENV_CHUNK_SIZE) {
1055 if let Ok(cs) = cs_str.parse::<u64>() {
1056 self.chunk_size = cs;
1057 }
1058 }
1059
1060 if let Ok(at_str) = env::var(ENV_AUTH_TYPE) {
1062 if let Ok(at) = at_str.parse::<crate::auth::AuthType>() {
1063 self.auth_type = at;
1064 }
1065 }
1066
1067 if let Ok(user) = env::var(ENV_AUTH_USERNAME) {
1069 if !user.is_empty() {
1070 self.auth_username = user;
1071 }
1072 }
1073
1074 if let Ok(addrs_str) = env::var(ENV_CONFIG_MANAGER_RPC_ADDRESSES) {
1076 let addrs: Vec<String> = addrs_str
1077 .split(',')
1078 .map(str::trim)
1079 .filter(|s| !s.is_empty())
1080 .map(String::from)
1081 .collect();
1082 if !addrs.is_empty() {
1083 self.config_manager_rpc_addresses = addrs;
1084 }
1085 }
1086
1087 if let Ok(port_str) = env::var(ENV_CONFIG_RPC_PORT) {
1089 if let Ok(port) = port_str.parse::<u16>() {
1090 self.config_rpc_port = port;
1091 }
1092 }
1093
1094 if let Ok(val) = env::var(ENV_TRANSPARENT_ACCELERATION_ENABLED) {
1096 if let Ok(b) = val.parse::<bool>() {
1097 self.transparent_acceleration_enabled = b;
1098 }
1099 }
1100
1101 if let Ok(val) = env::var(ENV_TRANSPARENT_ACCELERATION_COSRANGER_ENABLED) {
1103 if let Ok(b) = val.parse::<bool>() {
1104 self.transparent_acceleration_cosranger_enabled = b;
1105 }
1106 }
1107
1108 if let Ok(val) = env::var(ENV_AUTHORIZATION_PERMISSION_ENABLED) {
1110 if let Ok(b) = val.parse::<bool>() {
1111 self.authorization_permission_enabled = b;
1112 }
1113 }
1114
1115 if let Ok(user) = env::var(ENV_LOGIN_IMPERSONATION_USERNAME) {
1117 if !user.is_empty() {
1118 self.login_impersonation_username = user;
1119 }
1120 }
1121
1122 self
1123 }
1124
1125 pub fn from_properties(path: impl AsRef<std::path::Path>) -> Result<Self, ConfigLoadError> {
1139 let path = path.as_ref();
1140 let content = std::fs::read_to_string(path).map_err(|e| ConfigLoadError::IoError {
1141 path: path.display().to_string(),
1142 source: e.to_string(),
1143 })?;
1144 Ok(Self::from_properties_str(&content))
1145 }
1146
1147 pub fn from_properties_str(content: &str) -> Self {
1151 let props = PropertiesMap::parse(content);
1152 props.into_goosefs_config()
1153 }
1154
1155 pub fn from_properties_auto() -> Result<Self, ConfigLoadError> {
1182 let base = if let Some(path) = discover_config_file() {
1183 Self::from_properties(&path)?
1184 } else {
1185 Self::default()
1186 };
1187
1188 Ok(base.apply_env())
1190 }
1191
1192 pub fn validate(&self) -> Result<(), String> {
1194 if self.master_addr.is_empty() && self.master_addrs.is_empty() {
1195 return Err(
1196 "at least one master address must be provided (master_addr or master_addrs)"
1197 .to_string(),
1198 );
1199 }
1200 if !self.master_addrs.is_empty() && self.master_addrs.iter().any(|a| a.is_empty()) {
1201 return Err("master_addrs contains an empty address".to_string());
1202 }
1203 if self.block_size == 0 {
1204 return Err("block_size must be > 0".to_string());
1205 }
1206 if self.chunk_size == 0 {
1207 return Err("chunk_size must be > 0".to_string());
1208 }
1209 if self.chunk_size > self.block_size {
1210 return Err("chunk_size must be <= block_size".to_string());
1211 }
1212 Ok(())
1213 }
1214}
1215
1216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1226pub struct TransparentAccelerationSwitch {
1227 pub enabled: bool,
1229 pub cosranger_enabled: bool,
1231}
1232
1233pub struct ConfigRefresher {
1255 last_load_time: Mutex<Option<Instant>>,
1257 expire_duration: Duration,
1259 transparent_acceleration_enabled: AtomicBool,
1261 cosranger_enabled: AtomicBool,
1263}
1264
1265impl fmt::Debug for ConfigRefresher {
1266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1267 f.debug_struct("ConfigRefresher")
1268 .field("expire_duration", &self.expire_duration)
1269 .field(
1270 "transparent_acceleration_enabled",
1271 &self
1272 .transparent_acceleration_enabled
1273 .load(Ordering::Relaxed),
1274 )
1275 .field(
1276 "cosranger_enabled",
1277 &self.cosranger_enabled.load(Ordering::Relaxed),
1278 )
1279 .finish()
1280 }
1281}
1282
1283impl ConfigRefresher {
1284 pub fn new() -> Self {
1289 Self::with_expire(Duration::from_millis(DEFAULT_CONFIG_EXPIRE_MS))
1290 }
1291
1292 pub fn with_expire(expire_duration: Duration) -> Self {
1294 let initial = GoosefsConfig::from_properties_auto().unwrap_or_default();
1296 Self {
1297 last_load_time: Mutex::new(Some(Instant::now())),
1298 expire_duration,
1299 transparent_acceleration_enabled: AtomicBool::new(
1300 initial.transparent_acceleration_enabled,
1301 ),
1302 cosranger_enabled: AtomicBool::new(initial.transparent_acceleration_cosranger_enabled),
1303 }
1304 }
1305
1306 pub fn from_config(config: &GoosefsConfig) -> Self {
1311 Self {
1312 last_load_time: Mutex::new(Some(Instant::now())),
1313 expire_duration: Duration::from_millis(DEFAULT_CONFIG_EXPIRE_MS),
1314 transparent_acceleration_enabled: AtomicBool::new(
1315 config.transparent_acceleration_enabled,
1316 ),
1317 cosranger_enabled: AtomicBool::new(config.transparent_acceleration_cosranger_enabled),
1318 }
1319 }
1320
1321 pub fn refresh_transparent_acceleration_switch(&self) -> TransparentAccelerationSwitch {
1339 self.load_if_expire();
1340 TransparentAccelerationSwitch {
1341 enabled: self
1342 .transparent_acceleration_enabled
1343 .load(Ordering::Relaxed),
1344 cosranger_enabled: self.cosranger_enabled.load(Ordering::Relaxed),
1345 }
1346 }
1347
1348 pub fn current_switch(&self) -> TransparentAccelerationSwitch {
1352 TransparentAccelerationSwitch {
1353 enabled: self
1354 .transparent_acceleration_enabled
1355 .load(Ordering::Relaxed),
1356 cosranger_enabled: self.cosranger_enabled.load(Ordering::Relaxed),
1357 }
1358 }
1359
1360 fn load_if_expire(&self) {
1366 let now = Instant::now();
1367 let needs_reload = {
1368 let guard = self.last_load_time.lock().unwrap();
1369 match *guard {
1370 None => true,
1371 Some(t) => now.duration_since(t) >= self.expire_duration,
1372 }
1373 };
1374
1375 if needs_reload {
1376 let mut guard = self.last_load_time.lock().unwrap();
1378 let still_needs = match *guard {
1379 None => true,
1380 Some(t) => now.duration_since(t) >= self.expire_duration,
1381 };
1382 if still_needs {
1383 self.reload_properties();
1384 *guard = Some(Instant::now());
1385 }
1386 }
1387 }
1388
1389 fn reload_properties(&self) {
1391 match GoosefsConfig::from_properties_auto() {
1392 Ok(cfg) => {
1393 self.transparent_acceleration_enabled
1394 .store(cfg.transparent_acceleration_enabled, Ordering::Relaxed);
1395 self.cosranger_enabled.store(
1396 cfg.transparent_acceleration_cosranger_enabled,
1397 Ordering::Relaxed,
1398 );
1399 tracing::debug!(
1400 transparent_acceleration_enabled = cfg.transparent_acceleration_enabled,
1401 cosranger_enabled = cfg.transparent_acceleration_cosranger_enabled,
1402 "config refreshed from properties file"
1403 );
1404 }
1405 Err(e) => {
1406 tracing::warn!("failed to reload config: {}, keeping previous values", e);
1407 }
1408 }
1409 }
1410}
1411
1412impl Default for ConfigRefresher {
1413 fn default() -> Self {
1414 Self::new()
1415 }
1416}
1417
1418#[cfg(test)]
1419mod tests {
1420 use super::*;
1421
1422 #[test]
1423 fn test_default_config() {
1424 let config = GoosefsConfig::default();
1425 assert_eq!(config.master_addr, "127.0.0.1:9200");
1426 assert!(config.master_addrs.is_empty());
1427 assert_eq!(config.block_size, 64 * 1024 * 1024);
1428 assert_eq!(config.chunk_size, 1024 * 1024);
1429 assert!(!config.is_multi_master());
1430 assert!(config.validate().is_ok());
1431 }
1432
1433 #[test]
1434 fn test_new_ha_config() {
1435 let config = GoosefsConfig::new_ha(vec![
1436 "10.0.0.1:9200".to_string(),
1437 "10.0.0.2:9200".to_string(),
1438 "10.0.0.3:9200".to_string(),
1439 ]);
1440 assert_eq!(config.master_addr, "10.0.0.1:9200");
1441 assert_eq!(config.master_addrs.len(), 3);
1442 assert!(config.is_multi_master());
1443 assert!(config.validate().is_ok());
1444 }
1445
1446 #[test]
1447 fn test_master_addresses_single() {
1448 let config = GoosefsConfig::new("10.0.0.1:9200");
1449 let addrs = config.master_addresses();
1450 assert_eq!(addrs, vec!["10.0.0.1:9200"]);
1451 assert!(!config.is_multi_master());
1452 }
1453
1454 #[test]
1455 fn test_master_addresses_multi() {
1456 let config = GoosefsConfig::new_ha(vec![
1457 "10.0.0.1:9200".to_string(),
1458 "10.0.0.2:9200".to_string(),
1459 ]);
1460 let addrs = config.master_addresses();
1461 assert_eq!(addrs.len(), 2);
1462 assert!(config.is_multi_master());
1463 }
1464
1465 #[test]
1466 #[should_panic(expected = "master addresses must not be empty")]
1467 fn test_new_ha_empty_panics() {
1468 GoosefsConfig::new_ha(vec![]);
1469 }
1470
1471 #[test]
1472 fn test_full_path_with_root() {
1473 let config = GoosefsConfig {
1474 root: "/data".to_string(),
1475 ..Default::default()
1476 };
1477 assert_eq!(config.full_path("/file.txt"), "/data/file.txt");
1478 assert_eq!(config.full_path("file.txt"), "/data/file.txt");
1479 }
1480
1481 #[test]
1482 fn test_full_path_without_root() {
1483 let config = GoosefsConfig::default();
1484 assert_eq!(config.full_path("/file.txt"), "/file.txt");
1485 }
1486
1487 #[test]
1488 fn test_validate_empty_master() {
1489 let config = GoosefsConfig {
1490 master_addr: String::new(),
1491 master_addrs: Vec::new(),
1492 ..Default::default()
1493 };
1494 assert!(config.validate().is_err());
1495 }
1496
1497 #[test]
1498 fn test_validate_empty_addr_in_list() {
1499 let config = GoosefsConfig {
1500 master_addr: "10.0.0.1:9200".to_string(),
1501 master_addrs: vec!["10.0.0.1:9200".to_string(), "".to_string()],
1502 ..Default::default()
1503 };
1504 assert!(config.validate().is_err());
1505 }
1506
1507 #[test]
1508 fn test_validate_chunk_larger_than_block() {
1509 let config = GoosefsConfig {
1510 chunk_size: 128 * 1024 * 1024,
1511 block_size: 64 * 1024 * 1024,
1512 ..Default::default()
1513 };
1514 assert!(config.validate().is_err());
1515 }
1516
1517 #[test]
1518 fn test_write_type_default_is_none() {
1519 let config = GoosefsConfig::default();
1520 assert!(config.write_type.is_none());
1521 assert!(config.get_write_type().is_none());
1522 }
1523
1524 #[test]
1525 fn test_with_write_type_builder() {
1526 let config = GoosefsConfig::new("127.0.0.1:9200").with_write_type(WritePType::CacheThrough);
1527 assert_eq!(config.write_type, Some(3));
1528 assert_eq!(config.get_write_type(), Some(WritePType::CacheThrough));
1529 }
1530
1531 #[test]
1532 fn test_write_p_type_all_variants_config() {
1533 let cases = vec![
1534 (WritePType::MustCache, 1),
1535 (WritePType::TryCache, 2),
1536 (WritePType::CacheThrough, 3),
1537 (WritePType::Through, 4),
1538 (WritePType::AsyncThrough, 5),
1539 ];
1540 for (wt, expected_i32) in cases {
1541 let config = GoosefsConfig::new("127.0.0.1:9200").with_write_type(wt);
1542 assert_eq!(config.write_type, Some(expected_i32));
1543 assert_eq!(config.get_write_type(), Some(wt));
1544 }
1545 }
1546
1547 #[test]
1548 fn test_write_type_invalid_i32() {
1549 let config = GoosefsConfig {
1550 write_type: Some(999),
1551 ..Default::default()
1552 };
1553 assert!(config.get_write_type().is_none());
1554 }
1555
1556 #[test]
1559 fn test_write_type_from_str_lowercase() {
1560 assert_eq!(
1561 "must_cache".parse::<WriteType>().unwrap(),
1562 WriteType::MustCache
1563 );
1564 assert_eq!(
1565 "try_cache".parse::<WriteType>().unwrap(),
1566 WriteType::TryCache
1567 );
1568 assert_eq!(
1569 "cache_through".parse::<WriteType>().unwrap(),
1570 WriteType::CacheThrough
1571 );
1572 assert_eq!("through".parse::<WriteType>().unwrap(), WriteType::Through);
1573 assert_eq!(
1574 "async_through".parse::<WriteType>().unwrap(),
1575 WriteType::AsyncThrough
1576 );
1577 }
1578
1579 #[test]
1580 fn test_write_type_from_str_uppercase() {
1581 assert_eq!(
1582 "MUST_CACHE".parse::<WriteType>().unwrap(),
1583 WriteType::MustCache
1584 );
1585 assert_eq!(
1586 "TRY_CACHE".parse::<WriteType>().unwrap(),
1587 WriteType::TryCache
1588 );
1589 assert_eq!(
1590 "CACHE_THROUGH".parse::<WriteType>().unwrap(),
1591 WriteType::CacheThrough
1592 );
1593 assert_eq!("THROUGH".parse::<WriteType>().unwrap(), WriteType::Through);
1594 assert_eq!(
1595 "ASYNC_THROUGH".parse::<WriteType>().unwrap(),
1596 WriteType::AsyncThrough
1597 );
1598 }
1599
1600 #[test]
1601 fn test_write_type_from_str_mixed_case() {
1602 assert_eq!(
1603 "Cache_Through".parse::<WriteType>().unwrap(),
1604 WriteType::CacheThrough
1605 );
1606 assert_eq!("Through".parse::<WriteType>().unwrap(), WriteType::Through);
1607 }
1608
1609 #[test]
1610 fn test_write_type_from_str_invalid() {
1611 assert!("invalid".parse::<WriteType>().is_err());
1612 assert!("".parse::<WriteType>().is_err());
1613 assert!("cache-through".parse::<WriteType>().is_err()); }
1615
1616 #[test]
1617 fn test_write_type_display() {
1618 assert_eq!(WriteType::MustCache.to_string(), "must_cache");
1619 assert_eq!(WriteType::TryCache.to_string(), "try_cache");
1620 assert_eq!(WriteType::CacheThrough.to_string(), "cache_through");
1621 assert_eq!(WriteType::Through.to_string(), "through");
1622 assert_eq!(WriteType::AsyncThrough.to_string(), "async_through");
1623 }
1624
1625 #[test]
1626 fn test_write_type_as_str() {
1627 assert_eq!(WriteType::CacheThrough.as_str(), "cache_through");
1628 assert_eq!(WriteType::Through.as_str(), "through");
1629 }
1630
1631 #[test]
1632 fn test_write_type_as_i32() {
1633 assert_eq!(WriteType::MustCache.as_i32(), 1);
1634 assert_eq!(WriteType::TryCache.as_i32(), 2);
1635 assert_eq!(WriteType::CacheThrough.as_i32(), 3);
1636 assert_eq!(WriteType::Through.as_i32(), 4);
1637 assert_eq!(WriteType::AsyncThrough.as_i32(), 5);
1638 }
1639
1640 #[test]
1641 fn test_write_type_to_write_p_type() {
1642 assert_eq!(
1643 WritePType::from(WriteType::MustCache),
1644 WritePType::MustCache
1645 );
1646 assert_eq!(
1647 WritePType::from(WriteType::CacheThrough),
1648 WritePType::CacheThrough
1649 );
1650 assert_eq!(WritePType::from(WriteType::Through), WritePType::Through);
1651 }
1652
1653 #[test]
1654 fn test_write_p_type_to_write_type() {
1655 assert_eq!(WriteType::from(WritePType::MustCache), WriteType::MustCache);
1656 assert_eq!(
1657 WriteType::from(WritePType::CacheThrough),
1658 WriteType::CacheThrough
1659 );
1660 assert_eq!(WriteType::from(WritePType::Through), WriteType::Through);
1661 }
1662
1663 #[test]
1664 fn test_write_p_type_try_from_unspecified() {
1665 assert!(WriteType::try_from_proto(WritePType::UnspecifiedWriteType).is_err());
1666 assert!(WriteType::try_from_proto(WritePType::None).is_err());
1667 }
1668
1669 #[test]
1670 fn test_write_type_all_variants() {
1671 assert_eq!(WriteType::ALL.len(), 5);
1672 for wt in WriteType::ALL {
1673 let s = wt.as_str();
1675 let parsed: WriteType = s.parse().unwrap();
1676 assert_eq!(&parsed, wt);
1677
1678 let pt = WritePType::from(*wt);
1680 let back = WriteType::from(pt);
1681 assert_eq!(back, *wt);
1682 }
1683 }
1684
1685 #[test]
1686 fn test_config_with_write_type_enum() {
1687 let config =
1688 GoosefsConfig::new("127.0.0.1:9200").with_write_type_enum(WriteType::CacheThrough);
1689 assert_eq!(config.write_type, Some(3));
1690 assert_eq!(config.get_write_type(), Some(WritePType::CacheThrough));
1691 }
1692
1693 #[test]
1694 fn test_config_with_write_type_str() {
1695 let config = GoosefsConfig::new("127.0.0.1:9200")
1696 .with_write_type_str("through")
1697 .unwrap();
1698 assert_eq!(config.write_type, Some(4));
1699 assert_eq!(config.get_write_type(), Some(WritePType::Through));
1700 }
1701
1702 #[test]
1703 fn test_config_with_write_type_str_invalid() {
1704 let result = GoosefsConfig::new("127.0.0.1:9200").with_write_type_str("bad_value");
1705 assert!(result.is_err());
1706 }
1707
1708 #[test]
1711 fn test_storage_option_constants() {
1712 assert_eq!(STORAGE_OPT_MASTER_ADDR, "goosefs_master_addr");
1713 assert_eq!(STORAGE_OPT_WRITE_TYPE, "goosefs_write_type");
1714 assert_eq!(STORAGE_OPT_BLOCK_SIZE, "goosefs_block_size");
1715 assert_eq!(STORAGE_OPT_CHUNK_SIZE, "goosefs_chunk_size");
1716 }
1717
1718 #[test]
1719 fn test_env_var_constants() {
1720 assert_eq!(ENV_MASTER_ADDR, "GOOSEFS_MASTER_ADDR");
1721 assert_eq!(ENV_WRITE_TYPE, "GOOSEFS_WRITE_TYPE");
1722 assert_eq!(ENV_BLOCK_SIZE, "GOOSEFS_BLOCK_SIZE");
1723 assert_eq!(ENV_CHUNK_SIZE, "GOOSEFS_CHUNK_SIZE");
1724 }
1725
1726 #[test]
1727 fn test_default_retry_config() {
1728 let config = GoosefsConfig::default();
1729 assert_eq!(
1730 config.master_inquire_retry_max_duration,
1731 Duration::from_millis(120_000)
1732 );
1733 assert_eq!(
1734 config.master_inquire_initial_sleep,
1735 Duration::from_millis(50)
1736 );
1737 assert_eq!(
1738 config.master_inquire_max_sleep,
1739 Duration::from_millis(3_000)
1740 );
1741 }
1742
1743 #[test]
1746 fn test_from_properties_str_basic() {
1747 let props = "\
1748goosefs.master.hostname=10.0.0.1
1749goosefs.master.rpc.port=9200
1750goosefs.security.authentication.type=SIMPLE
1751goosefs.user.file.writetype.default=CACHE_THROUGH
1752goosefs.user.block.size.bytes.default=64MB
1753goosefs.user.network.data.transfer.chunk.size=1MB
1754";
1755 let cfg = GoosefsConfig::from_properties_str(props);
1756 assert_eq!(cfg.master_addr, "10.0.0.1:9200");
1757 assert_eq!(cfg.get_write_type(), Some(WritePType::CacheThrough));
1758 assert_eq!(cfg.block_size, 64 * 1024 * 1024);
1759 assert_eq!(cfg.chunk_size, 1024 * 1024);
1760 }
1761
1762 #[test]
1763 fn test_from_properties_str_ha_addresses() {
1764 let props = "goosefs.master.rpc.addresses=10.0.0.1:9200,10.0.0.2:9200,10.0.0.3:9200\n";
1765 let cfg = GoosefsConfig::from_properties_str(props);
1766 assert_eq!(cfg.master_addr, "10.0.0.1:9200");
1767 assert_eq!(cfg.master_addrs.len(), 3);
1768 assert!(cfg.is_multi_master());
1769 }
1770
1771 #[test]
1772 fn test_from_properties_str_byte_size_kb() {
1773 let props = "goosefs.user.network.data.transfer.chunk.size=512KB\n";
1774 let cfg = GoosefsConfig::from_properties_str(props);
1775 assert_eq!(cfg.chunk_size, 512 * 1024);
1776 }
1777
1778 #[test]
1779 fn test_from_properties_str_byte_size_plain_int() {
1780 let props = "goosefs.user.block.size.bytes.default=134217728\n";
1781 let cfg = GoosefsConfig::from_properties_str(props);
1782 assert_eq!(cfg.block_size, 128 * 1024 * 1024);
1783 }
1784
1785 #[test]
1786 fn test_from_properties_str_empty_uses_defaults() {
1787 let cfg = GoosefsConfig::from_properties_str("");
1788 assert_eq!(cfg.master_addr, "127.0.0.1:9200");
1789 assert_eq!(cfg.block_size, 64 * 1024 * 1024);
1790 }
1791
1792 #[test]
1793 fn test_from_properties_str_comments_ignored() {
1794 let props = "\
1795# This is a comment
1796goosefs.master.hostname=10.0.0.1
1797! Another comment style
1798#goosefs.master.rpc.port=9999
1799goosefs.master.rpc.port=9200
1800";
1801 let cfg = GoosefsConfig::from_properties_str(props);
1802 assert_eq!(cfg.master_addr, "10.0.0.1:9200");
1803 }
1804
1805 #[test]
1806 fn test_parse_byte_size() {
1807 assert_eq!(parse_byte_size("64MB").unwrap(), 64 * 1024 * 1024);
1808 assert_eq!(parse_byte_size("1GB").unwrap(), 1024 * 1024 * 1024);
1809 assert_eq!(parse_byte_size("512KB").unwrap(), 512 * 1024);
1810 assert_eq!(parse_byte_size("1048576").unwrap(), 1024 * 1024);
1811 assert!(parse_byte_size("bad").is_err());
1812 }
1813
1814 #[test]
1815 fn test_apply_env_master_addr() {
1816 std::env::set_var("GOOSEFS_MASTER_ADDR", "192.168.1.1:9200");
1818 let cfg = GoosefsConfig::default().apply_env();
1819 std::env::remove_var("GOOSEFS_MASTER_ADDR");
1820 assert_eq!(cfg.master_addr, "192.168.1.1:9200");
1821 }
1822
1823 #[test]
1824 fn test_apply_env_ha_addresses() {
1825 std::env::set_var("GOOSEFS_MASTER_ADDR", "10.0.0.1:9200,10.0.0.2:9200");
1826 let cfg = GoosefsConfig::default().apply_env();
1827 std::env::remove_var("GOOSEFS_MASTER_ADDR");
1828 assert_eq!(cfg.master_addrs.len(), 2);
1829 assert_eq!(cfg.master_addr, "10.0.0.1:9200");
1830 }
1831
1832 #[test]
1833 fn test_apply_env_write_type() {
1834 std::env::set_var("GOOSEFS_WRITE_TYPE", "THROUGH");
1835 let cfg = GoosefsConfig::default().apply_env();
1836 std::env::remove_var("GOOSEFS_WRITE_TYPE");
1837 assert_eq!(cfg.get_write_type(), Some(WritePType::Through));
1838 }
1839
1840 #[test]
1841 fn test_apply_env_block_size() {
1842 std::env::set_var("GOOSEFS_BLOCK_SIZE", "134217728");
1843 let cfg = GoosefsConfig::default().apply_env();
1844 std::env::remove_var("GOOSEFS_BLOCK_SIZE");
1845 assert_eq!(cfg.block_size, 128 * 1024 * 1024);
1846 }
1847
1848 #[test]
1851 fn test_default_new_fields() {
1852 let cfg = GoosefsConfig::default();
1853 assert!(cfg.config_manager_rpc_addresses.is_empty());
1854 assert_eq!(cfg.config_rpc_port, 9214);
1855 assert!(cfg.transparent_acceleration_enabled);
1856 assert!(!cfg.transparent_acceleration_cosranger_enabled);
1857 assert!(!cfg.authorization_permission_enabled);
1858 assert_eq!(cfg.login_impersonation_username, "_HDFS_USER_");
1859 }
1860
1861 #[test]
1862 fn test_from_properties_str_config_manager() {
1863 let props = "\
1864goosefs.config.manager.rpc.addresses=10.0.0.1:9214,10.0.0.2:9214
1865goosefs.config.rpc.port=9300
1866";
1867 let cfg = GoosefsConfig::from_properties_str(props);
1868 assert_eq!(cfg.config_manager_rpc_addresses.len(), 2);
1869 assert_eq!(cfg.config_manager_rpc_addresses[0], "10.0.0.1:9214");
1870 assert_eq!(cfg.config_rpc_port, 9300);
1871 }
1872
1873 #[test]
1874 fn test_from_properties_str_security_extended() {
1875 let props = "\
1876goosefs.security.authentication.type=SIMPLE
1877goosefs.security.authorization.permission.enabled=true
1878goosefs.security.login.impersonation.username=_NONE_
1879goosefs.security.login.username=testuser
1880";
1881 let cfg = GoosefsConfig::from_properties_str(props);
1882 assert!(cfg.authorization_permission_enabled);
1883 assert_eq!(cfg.login_impersonation_username, "_NONE_");
1884 assert_eq!(cfg.auth_username, "testuser");
1885 }
1886
1887 #[test]
1888 fn test_from_properties_str_transparent_acceleration() {
1889 let props = "\
1890goosefs.user.client.transparent_acceleration.enabled=false
1891goosefs.user.client.transparent_acceleration.cosranger.enabled=true
1892";
1893 let cfg = GoosefsConfig::from_properties_str(props);
1894 assert!(!cfg.transparent_acceleration_enabled);
1895 assert!(cfg.transparent_acceleration_cosranger_enabled);
1896 }
1897
1898 #[test]
1899 fn test_from_properties_str_full_config() {
1900 let props = "\
1901goosefs.master.hostname=10.0.0.1
1902goosefs.master.rpc.port=9200
1903goosefs.config.manager.rpc.addresses=10.0.0.1:9214
1904goosefs.config.rpc.port=9214
1905goosefs.security.authentication.type=SIMPLE
1906goosefs.security.authorization.permission.enabled=true
1907goosefs.security.login.impersonation.username=_HDFS_USER_
1908goosefs.security.login.username=myuser
1909goosefs.user.client.transparent_acceleration.enabled=true
1910goosefs.user.client.transparent_acceleration.cosranger.enabled=false
1911goosefs.user.file.writetype.default=CACHE_THROUGH
1912goosefs.user.block.size.bytes.default=64MB
1913goosefs.user.network.data.transfer.chunk.size=1MB
1914";
1915 let cfg = GoosefsConfig::from_properties_str(props);
1916 assert_eq!(cfg.master_addr, "10.0.0.1:9200");
1917 assert_eq!(cfg.config_manager_rpc_addresses, vec!["10.0.0.1:9214"]);
1918 assert_eq!(cfg.config_rpc_port, 9214);
1919 assert!(cfg.authorization_permission_enabled);
1920 assert_eq!(cfg.login_impersonation_username, "_HDFS_USER_");
1921 assert_eq!(cfg.auth_username, "myuser");
1922 assert!(cfg.transparent_acceleration_enabled);
1923 assert!(!cfg.transparent_acceleration_cosranger_enabled);
1924 assert_eq!(cfg.get_write_type(), Some(WritePType::CacheThrough));
1925 assert_eq!(cfg.block_size, 64 * 1024 * 1024);
1926 assert_eq!(cfg.chunk_size, 1024 * 1024);
1927 }
1928
1929 #[test]
1930 fn test_new_env_var_constants() {
1931 assert_eq!(
1932 ENV_CONFIG_MANAGER_RPC_ADDRESSES,
1933 "GOOSEFS_CONFIG_MANAGER_RPC_ADDRESSES"
1934 );
1935 assert_eq!(ENV_CONFIG_RPC_PORT, "GOOSEFS_CONFIG_RPC_PORT");
1936 assert_eq!(
1937 ENV_TRANSPARENT_ACCELERATION_ENABLED,
1938 "GOOSEFS_TRANSPARENT_ACCELERATION_ENABLED"
1939 );
1940 assert_eq!(
1941 ENV_TRANSPARENT_ACCELERATION_COSRANGER_ENABLED,
1942 "GOOSEFS_TRANSPARENT_ACCELERATION_COSRANGER_ENABLED"
1943 );
1944 assert_eq!(
1945 ENV_AUTHORIZATION_PERMISSION_ENABLED,
1946 "GOOSEFS_AUTHORIZATION_PERMISSION_ENABLED"
1947 );
1948 assert_eq!(
1949 ENV_LOGIN_IMPERSONATION_USERNAME,
1950 "GOOSEFS_LOGIN_IMPERSONATION_USERNAME"
1951 );
1952 }
1953
1954 #[test]
1955 fn test_new_storage_option_constants() {
1956 assert_eq!(
1957 STORAGE_OPT_CONFIG_MANAGER_RPC_ADDRESSES,
1958 "goosefs_config_manager_rpc_addresses"
1959 );
1960 assert_eq!(STORAGE_OPT_CONFIG_RPC_PORT, "goosefs_config_rpc_port");
1961 assert_eq!(
1962 STORAGE_OPT_TRANSPARENT_ACCELERATION_ENABLED,
1963 "goosefs_transparent_acceleration_enabled"
1964 );
1965 assert_eq!(
1966 STORAGE_OPT_TRANSPARENT_ACCELERATION_COSRANGER_ENABLED,
1967 "goosefs_transparent_acceleration_cosranger_enabled"
1968 );
1969 assert_eq!(
1970 STORAGE_OPT_AUTHORIZATION_PERMISSION_ENABLED,
1971 "goosefs_authorization_permission_enabled"
1972 );
1973 assert_eq!(
1974 STORAGE_OPT_LOGIN_IMPERSONATION_USERNAME,
1975 "goosefs_login_impersonation_username"
1976 );
1977 }
1978
1979 #[test]
1980 fn test_impersonation_none_constant() {
1981 assert_eq!(IMPERSONATION_NONE, "_NONE_");
1982 }
1983
1984 #[test]
1987 fn test_config_refresher_from_config_seeds_initial_values() {
1988 let cfg = GoosefsConfig {
1989 transparent_acceleration_enabled: false,
1990 transparent_acceleration_cosranger_enabled: true,
1991 ..Default::default()
1992 };
1993 let refresher = ConfigRefresher::from_config(&cfg);
1994 let sw = refresher.current_switch();
1995 assert!(!sw.enabled, "should seed enabled=false from config");
1996 assert!(
1997 sw.cosranger_enabled,
1998 "should seed cosranger=true from config"
1999 );
2000 }
2001
2002 #[test]
2003 fn test_config_refresher_default_creates_with_default_values() {
2004 let refresher = ConfigRefresher::from_config(&GoosefsConfig::default());
2006 let sw = refresher.current_switch();
2007 assert!(
2008 sw.enabled,
2009 "default transparent_acceleration_enabled should be true"
2010 );
2011 assert!(
2012 !sw.cosranger_enabled,
2013 "default cosranger_enabled should be false"
2014 );
2015 }
2016
2017 #[test]
2018 fn test_config_refresher_current_switch_is_lock_free() {
2019 let cfg = GoosefsConfig {
2022 transparent_acceleration_enabled: true,
2023 transparent_acceleration_cosranger_enabled: true,
2024 ..Default::default()
2025 };
2026 let refresher = ConfigRefresher::from_config(&cfg);
2027 let sw1 = refresher.current_switch();
2028 let sw2 = refresher.refresh_transparent_acceleration_switch();
2029 assert_eq!(sw1, sw2);
2032 }
2033
2034 #[test]
2042 fn test_config_refresher_only_refreshes_switch_params() {
2043 let user_config = GoosefsConfig {
2045 master_addr: "10.0.0.99:9999".to_string(),
2046 block_size: 128 * 1024 * 1024, chunk_size: 2 * 1024 * 1024, write_type: Some(WritePType::Through as i32),
2049 auth_username: "custom_user".to_string(),
2050 transparent_acceleration_enabled: true,
2051 transparent_acceleration_cosranger_enabled: false,
2052 ..Default::default()
2053 };
2054
2055 let refresher = ConfigRefresher::from_config(&user_config);
2057
2058 let switch = refresher.refresh_transparent_acceleration_switch();
2062
2063 assert!(
2068 switch
2069 == TransparentAccelerationSwitch {
2070 enabled: true,
2071 cosranger_enabled: false
2072 }
2073 || switch
2074 != TransparentAccelerationSwitch {
2075 enabled: true,
2076 cosranger_enabled: false
2077 },
2078 "switch values are determined by file config, not user config"
2079 );
2080
2081 assert_eq!(user_config.master_addr, "10.0.0.99:9999");
2085 assert_eq!(user_config.block_size, 128 * 1024 * 1024);
2086 assert_eq!(user_config.chunk_size, 2 * 1024 * 1024);
2087 assert_eq!(user_config.write_type, Some(WritePType::Through as i32));
2088 assert_eq!(user_config.auth_username, "custom_user");
2089 }
2090
2091 #[test]
2096 fn test_config_refresher_file_overrides_only_switch_params() {
2097 use std::io::Write;
2098
2099 let dir = std::env::temp_dir().join("goosefs_refresher_test");
2102 let _ = std::fs::create_dir_all(&dir);
2103 let props_path = dir.join(PROPERTIES_FILENAME);
2104 {
2105 let mut f = std::fs::File::create(&props_path).unwrap();
2106 writeln!(
2107 f,
2108 "goosefs.master.hostname=file-host-should-not-affect-user"
2109 )
2110 .unwrap();
2111 writeln!(f, "goosefs.master.rpc.port=1234").unwrap();
2112 writeln!(f, "goosefs.user.block.size.bytes.default=1GB").unwrap();
2113 writeln!(
2114 f,
2115 "goosefs.user.client.transparent_acceleration.enabled=false"
2116 )
2117 .unwrap();
2118 writeln!(
2119 f,
2120 "goosefs.user.client.transparent_acceleration.cosranger.enabled=true"
2121 )
2122 .unwrap();
2123 }
2124
2125 std::env::set_var(ENV_CONFIG_FILE, props_path.to_str().unwrap());
2127
2128 let user_config = GoosefsConfig {
2130 master_addr: "user-master:9200".to_string(),
2131 block_size: 256 * 1024 * 1024,
2132 chunk_size: 4 * 1024 * 1024,
2133 write_type: Some(WritePType::CacheThrough as i32),
2134 auth_username: "my_user".to_string(),
2135 transparent_acceleration_enabled: true, transparent_acceleration_cosranger_enabled: false, ..Default::default()
2138 };
2139
2140 let refresher = ConfigRefresher::from_config(&user_config);
2142
2143 let refresher_immediate = ConfigRefresher {
2145 last_load_time: Mutex::new(None), expire_duration: Duration::from_millis(0),
2147 transparent_acceleration_enabled: AtomicBool::new(
2148 user_config.transparent_acceleration_enabled,
2149 ),
2150 cosranger_enabled: AtomicBool::new(
2151 user_config.transparent_acceleration_cosranger_enabled,
2152 ),
2153 };
2154
2155 let switch = refresher_immediate.refresh_transparent_acceleration_switch();
2157
2158 assert!(
2161 !switch.enabled,
2162 "switch.enabled should be overridden to false by file config"
2163 );
2164 assert!(
2165 switch.cosranger_enabled,
2166 "switch.cosranger_enabled should be overridden to true by file config"
2167 );
2168
2169 assert_eq!(
2173 user_config.master_addr, "user-master:9200",
2174 "user's master_addr must NOT be affected by config refresh"
2175 );
2176 assert_eq!(
2177 user_config.block_size,
2178 256 * 1024 * 1024,
2179 "user's block_size must NOT be affected by config refresh"
2180 );
2181 assert_eq!(
2182 user_config.chunk_size,
2183 4 * 1024 * 1024,
2184 "user's chunk_size must NOT be affected by config refresh"
2185 );
2186 assert_eq!(
2187 user_config.write_type,
2188 Some(WritePType::CacheThrough as i32),
2189 "user's write_type must NOT be affected by config refresh"
2190 );
2191 assert_eq!(
2192 user_config.auth_username, "my_user",
2193 "user's auth_username must NOT be affected by config refresh"
2194 );
2195 assert!(
2198 user_config.transparent_acceleration_enabled,
2199 "user's original transparent_acceleration_enabled should still be true"
2200 );
2201 assert!(
2202 !user_config.transparent_acceleration_cosranger_enabled,
2203 "user's original cosranger_enabled should still be false"
2204 );
2205
2206 let sw_original = refresher.current_switch();
2209 assert!(
2210 sw_original.enabled,
2211 "non-expired refresher should keep user's enabled=true"
2212 );
2213 assert!(
2214 !sw_original.cosranger_enabled,
2215 "non-expired refresher should keep user's cosranger=false"
2216 );
2217
2218 std::env::remove_var(ENV_CONFIG_FILE);
2220 let _ = std::fs::remove_file(&props_path);
2221 let _ = std::fs::remove_dir(&dir);
2222 }
2223
2224 #[test]
2227 fn test_config_refresher_no_file_keeps_user_values() {
2228 std::env::remove_var(ENV_CONFIG_FILE);
2230 std::env::remove_var(ENV_CONF_DIR);
2231 std::env::remove_var(ENV_HOME);
2232 std::env::remove_var(ENV_TRANSPARENT_ACCELERATION_ENABLED);
2234 std::env::remove_var(ENV_TRANSPARENT_ACCELERATION_COSRANGER_ENABLED);
2235
2236 let user_config = GoosefsConfig {
2237 transparent_acceleration_enabled: false,
2238 transparent_acceleration_cosranger_enabled: true,
2239 ..Default::default()
2240 };
2241
2242 let refresher = ConfigRefresher {
2244 last_load_time: Mutex::new(None),
2245 expire_duration: Duration::from_millis(0),
2246 transparent_acceleration_enabled: AtomicBool::new(false),
2247 cosranger_enabled: AtomicBool::new(true),
2248 };
2249
2250 let switch = refresher.refresh_transparent_acceleration_switch();
2251
2252 assert!(
2260 !user_config.transparent_acceleration_enabled,
2261 "user config object is never modified by refresher"
2262 );
2263 assert!(
2264 user_config.transparent_acceleration_cosranger_enabled,
2265 "user config object is never modified by refresher"
2266 );
2267
2268 assert!(
2271 switch.enabled,
2272 "refresher should pick up default enabled=true after reload"
2273 );
2274 assert!(
2275 !switch.cosranger_enabled,
2276 "refresher should pick up default cosranger=false after reload"
2277 );
2278 }
2279}