Skip to content

Commit 03045ff

Browse files
marshawcocohousemeclaudeloverustfsovertrue
authored
fix(iam): keep error state on initial load failure (rustfs#2846)
Signed-off-by: houseme <housemecn@gmail.com> Co-authored-by: houseme <housemecn@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: loverustfs <hello@rustfs.com> Co-authored-by: 安正超 <anzhengchao@gmail.com>
1 parent 61bd569 commit 03045ff

3 files changed

Lines changed: 182 additions & 21 deletions

File tree

crates/iam/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub async fn init_iam_sys(ecstore: Arc<ECStore>) -> Result<()> {
4848

4949
// 2. Create the cache manager.
5050
// The `new` method now performs a blocking initial load from disk.
51-
let cache_manager = IamCache::new(storage_adapter).await;
51+
let cache_manager = IamCache::new(storage_adapter).await?;
5252

5353
// 3. Construct the system interface
5454
let iam_instance = Arc::new(IamSys::new(cache_manager));

crates/iam/src/manager.rs

Lines changed: 165 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ use tracing::{error, info};
5858

5959
const IAM_FORMAT_FILE: &str = "format.json";
6060
const IAM_FORMAT_VERSION_1: i32 = 1;
61+
#[cfg(not(test))]
62+
const INITIAL_LOAD_RETRY_DELAY: Duration = Duration::from_secs(1);
63+
#[cfg(test)]
64+
const INITIAL_LOAD_RETRY_DELAY: Duration = Duration::from_millis(1);
6165

6266
#[derive(Serialize, Deserialize)]
6367
struct IAMFormat {
@@ -116,7 +120,7 @@ where
116120
///
117121
/// # Returns
118122
/// An Arc-wrapped instance of IamSystem
119-
pub(crate) async fn new(api: T) -> Arc<Self> {
123+
pub(crate) async fn new(api: T) -> Result<Arc<Self>> {
120124
let (sender, receiver) = mpsc::channel::<i64>(100);
121125

122126
let sys = Arc::new(Self {
@@ -132,8 +136,8 @@ where
132136
last_sync_duration_millis: AtomicU64::new(0),
133137
});
134138

135-
sys.clone().init(receiver).await.unwrap();
136-
sys
139+
sys.clone().init(receiver).await?;
140+
Ok(sys)
137141
}
138142

139143
/// Initialize the IAM system
@@ -144,19 +148,26 @@ where
144148

145149
// Critical: Load all existing users/policies into memory cache
146150
const MAX_RETRIES: usize = 3;
151+
let mut load_error = None;
147152
for attempt in 0..MAX_RETRIES {
148153
if let Err(e) = self.clone().load().await {
149154
if attempt == MAX_RETRIES - 1 {
150155
self.state.store(IamState::Error as u8, Ordering::SeqCst);
151156
warn!("IAM failed to load initial data after {} attempts: {:?}", MAX_RETRIES, e);
157+
load_error = Some(e);
152158
} else {
153159
warn!("IAM load failed, retrying... attempt {}", attempt + 1);
154-
tokio::time::sleep(Duration::from_secs(1)).await;
160+
tokio::time::sleep(INITIAL_LOAD_RETRY_DELAY).await;
155161
}
156162
} else {
157163
break;
158164
}
159165
}
166+
167+
if let Some(err) = load_error {
168+
return Err(err);
169+
}
170+
160171
self.state.store(IamState::Ready as u8, Ordering::SeqCst);
161172
info!("IAM System successfully initialized and marked as READY");
162173

@@ -2024,6 +2035,156 @@ mod tests {
20242035
use serde_json::json;
20252036
use std::collections::HashMap;
20262037

2038+
#[derive(Clone)]
2039+
struct FailingInitialLoadStore;
2040+
2041+
#[async_trait::async_trait]
2042+
impl Store for FailingInitialLoadStore {
2043+
fn has_watcher(&self) -> bool {
2044+
false
2045+
}
2046+
2047+
async fn save_iam_config<Item: Serialize + Send>(&self, _item: Item, _path: impl AsRef<str> + Send) -> Result<()> {
2048+
Ok(())
2049+
}
2050+
2051+
async fn load_iam_config<Item: serde::de::DeserializeOwned>(&self, _path: impl AsRef<str> + Send) -> Result<Item> {
2052+
Err(Error::ConfigNotFound)
2053+
}
2054+
2055+
async fn delete_iam_config(&self, _path: impl AsRef<str> + Send) -> Result<()> {
2056+
Err(Error::InvalidArgument)
2057+
}
2058+
2059+
async fn save_user_identity(
2060+
&self,
2061+
_name: &str,
2062+
_user_type: UserType,
2063+
_item: UserIdentity,
2064+
_ttl: Option<usize>,
2065+
) -> Result<()> {
2066+
Err(Error::InvalidArgument)
2067+
}
2068+
2069+
async fn delete_user_identity(&self, _name: &str, _user_type: UserType) -> Result<()> {
2070+
Err(Error::InvalidArgument)
2071+
}
2072+
2073+
async fn load_user_identity(&self, _name: &str, _user_type: UserType) -> Result<UserIdentity> {
2074+
Err(Error::InvalidArgument)
2075+
}
2076+
2077+
async fn load_user(&self, _name: &str, _user_type: UserType, _m: &mut HashMap<String, UserIdentity>) -> Result<()> {
2078+
Err(Error::InvalidArgument)
2079+
}
2080+
2081+
async fn load_users(&self, _user_type: UserType, _m: &mut HashMap<String, UserIdentity>) -> Result<()> {
2082+
Err(Error::InvalidArgument)
2083+
}
2084+
2085+
async fn load_secret_key(&self, _name: &str, _user_type: UserType) -> Result<String> {
2086+
Err(Error::InvalidArgument)
2087+
}
2088+
2089+
async fn save_group_info(&self, _name: &str, _item: GroupInfo) -> Result<()> {
2090+
Err(Error::InvalidArgument)
2091+
}
2092+
2093+
async fn delete_group_info(&self, _name: &str) -> Result<()> {
2094+
Err(Error::InvalidArgument)
2095+
}
2096+
2097+
async fn load_group(&self, _name: &str, _m: &mut HashMap<String, GroupInfo>) -> Result<()> {
2098+
Err(Error::InvalidArgument)
2099+
}
2100+
2101+
async fn load_groups(&self, _m: &mut HashMap<String, GroupInfo>) -> Result<()> {
2102+
Err(Error::InvalidArgument)
2103+
}
2104+
2105+
async fn save_policy_doc(&self, _name: &str, _item: PolicyDoc) -> Result<()> {
2106+
Err(Error::InvalidArgument)
2107+
}
2108+
2109+
async fn delete_policy_doc(&self, _name: &str) -> Result<()> {
2110+
Err(Error::InvalidArgument)
2111+
}
2112+
2113+
async fn load_policy(&self, _name: &str) -> Result<PolicyDoc> {
2114+
Err(Error::InvalidArgument)
2115+
}
2116+
2117+
async fn load_policy_doc(&self, _name: &str, _m: &mut HashMap<String, PolicyDoc>) -> Result<()> {
2118+
Err(Error::InvalidArgument)
2119+
}
2120+
2121+
async fn load_policy_docs(&self, _m: &mut HashMap<String, PolicyDoc>) -> Result<()> {
2122+
Err(Error::InvalidArgument)
2123+
}
2124+
2125+
async fn save_mapped_policy(
2126+
&self,
2127+
_name: &str,
2128+
_user_type: UserType,
2129+
_is_group: bool,
2130+
_item: MappedPolicy,
2131+
_ttl: Option<usize>,
2132+
) -> Result<()> {
2133+
Err(Error::InvalidArgument)
2134+
}
2135+
2136+
async fn delete_mapped_policy(&self, _name: &str, _user_type: UserType, _is_group: bool) -> Result<()> {
2137+
Err(Error::InvalidArgument)
2138+
}
2139+
2140+
async fn load_mapped_policy(
2141+
&self,
2142+
_name: &str,
2143+
_user_type: UserType,
2144+
_is_group: bool,
2145+
_m: &mut HashMap<String, MappedPolicy>,
2146+
) -> Result<()> {
2147+
Err(Error::InvalidArgument)
2148+
}
2149+
2150+
async fn load_mapped_policies(
2151+
&self,
2152+
_user_type: UserType,
2153+
_is_group: bool,
2154+
_m: &mut HashMap<String, MappedPolicy>,
2155+
) -> Result<()> {
2156+
Err(Error::InvalidArgument)
2157+
}
2158+
2159+
async fn load_all(&self, _cache: &Cache) -> Result<()> {
2160+
Err(Error::Io(std::io::Error::other("initial load failed")))
2161+
}
2162+
}
2163+
2164+
#[tokio::test]
2165+
async fn test_init_keeps_error_state_when_initial_load_fails() {
2166+
let (sender, receiver) = mpsc::channel::<i64>(1);
2167+
let sys = Arc::new(IamCache {
2168+
api: FailingInitialLoadStore,
2169+
cache: Cache::default(),
2170+
state: Arc::new(AtomicU8::new(IamState::Uninitialized as u8)),
2171+
loading: Arc::new(AtomicBool::new(false)),
2172+
send_chan: sender,
2173+
roles: HashMap::new(),
2174+
last_timestamp: AtomicI64::new(0),
2175+
sync_failures: AtomicU64::new(0),
2176+
sync_successes: AtomicU64::new(0),
2177+
last_sync_duration_millis: AtomicU64::new(0),
2178+
});
2179+
2180+
let result = Arc::clone(&sys).init(receiver).await;
2181+
2182+
assert!(matches!(result, Err(Error::Io(_))));
2183+
assert!(!sys.is_ready());
2184+
assert_eq!(sys.state.load(Ordering::SeqCst), IamState::Error as u8);
2185+
assert_eq!(sys.sync_failures.load(Ordering::Relaxed), 3);
2186+
}
2187+
20272188
#[test]
20282189
fn test_iam_format_new_version_1() {
20292190
let format = IAMFormat::new_version_1();

crates/iam/src/sys.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,7 +1565,7 @@ mod tests {
15651565
ensure_test_global_credentials();
15661566

15671567
let store = StsTestMockStore { empty_policies: false };
1568-
let cache_manager = IamCache::new(store).await;
1568+
let cache_manager = IamCache::new(store).await.unwrap();
15691569
let iam_sys = IamSys::new(cache_manager);
15701570

15711571
let (cred, _) = iam_sys
@@ -1588,7 +1588,7 @@ mod tests {
15881588
ensure_test_global_credentials();
15891589

15901590
let store = StsTestMockStore { empty_policies: false };
1591-
let cache_manager = IamCache::new(store).await;
1591+
let cache_manager = IamCache::new(store).await.unwrap();
15921592
let iam_sys = IamSys::new(cache_manager);
15931593

15941594
let initial_expiration = OffsetDateTime::now_utc() + time::Duration::hours(2);
@@ -1641,7 +1641,7 @@ mod tests {
16411641
ensure_test_global_credentials();
16421642

16431643
let store = StsTestMockStore { empty_policies: false };
1644-
let cache_manager = IamCache::new(store).await;
1644+
let cache_manager = IamCache::new(store).await.unwrap();
16451645
let iam_sys = IamSys::new(cache_manager);
16461646

16471647
let (cred, _) = iam_sys
@@ -1682,7 +1682,7 @@ mod tests {
16821682
ensure_test_global_credentials();
16831683

16841684
let store = StsTestMockStore { empty_policies: false };
1685-
let cache_manager = IamCache::new(store).await;
1685+
let cache_manager = IamCache::new(store).await.unwrap();
16861686
let iam_sys = IamSys::new(cache_manager);
16871687

16881688
let parent_user = "sts-fallback-test-parent";
@@ -1763,7 +1763,7 @@ mod tests {
17631763
ensure_test_global_credentials();
17641764

17651765
let store = StsTestMockStore { empty_policies: false };
1766-
let cache_manager = IamCache::new(store).await;
1766+
let cache_manager = IamCache::new(store).await.unwrap();
17671767
let iam_sys = IamSys::new(cache_manager);
17681768

17691769
let parent_user = "sts-fallback-test-parent";
@@ -1867,7 +1867,7 @@ mod tests {
18671867
#[tokio::test]
18681868
async fn test_sts_groups_fallback_temp_creds_receive_parent_group_policies() {
18691869
let store = StsTestMockStore { empty_policies: false };
1870-
let cache_manager = IamCache::new(store).await;
1870+
let cache_manager = IamCache::new(store).await.unwrap();
18711871
let iam_sys = IamSys::new(cache_manager);
18721872

18731873
let parent_user = "sts-fallback-test-parent";
@@ -1898,7 +1898,7 @@ mod tests {
18981898
#[tokio::test]
18991899
async fn test_sts_deny_only_session_policy_deny_blocks_when_iam_policies_empty() {
19001900
let store = StsTestMockStore { empty_policies: true };
1901-
let cache_manager = IamCache::new(store).await;
1901+
let cache_manager = IamCache::new(store).await.unwrap();
19021902
let iam_sys = IamSys::new(cache_manager);
19031903

19041904
let parent_user = "sts-empty-parent-policy-test";
@@ -1938,7 +1938,7 @@ mod tests {
19381938
#[tokio::test]
19391939
async fn test_sts_deny_only_session_policy_allow_when_no_deny_on_action() {
19401940
let store = StsTestMockStore { empty_policies: true };
1941-
let cache_manager = IamCache::new(store).await;
1941+
let cache_manager = IamCache::new(store).await.unwrap();
19421942
let iam_sys = IamSys::new(cache_manager);
19431943

19441944
let parent_user = "sts-empty-parent-policy-test";
@@ -1982,7 +1982,7 @@ mod tests {
19821982
#[tokio::test]
19831983
async fn test_load_user_notification_populates_user_and_policy_caches() {
19841984
let store = StsTestMockStore { empty_policies: false };
1985-
let cache_manager = IamCache::new(store).await;
1985+
let cache_manager = IamCache::new(store).await.unwrap();
19861986
let iam_sys = IamSys::new(cache_manager);
19871987

19881988
iam_sys.load_user("notify-user", UserType::Reg).await.unwrap();
@@ -2003,7 +2003,7 @@ mod tests {
20032003
#[tokio::test]
20042004
async fn test_check_key_propagates_cache_miss_load_failure() {
20052005
let store = StsTestMockStore { empty_policies: false };
2006-
let cache_manager = IamCache::new(store).await;
2006+
let cache_manager = IamCache::new(store).await.unwrap();
20072007
let iam_sys = IamSys::new(cache_manager);
20082008

20092009
let result = iam_sys.check_key("load-failure-user").await;
@@ -2014,7 +2014,7 @@ mod tests {
20142014
#[tokio::test]
20152015
async fn test_prepare_auth_eval_matches_prepare_sts_auth_for_parent_policy_fallback() {
20162016
let store = StsTestMockStore { empty_policies: false };
2017-
let cache_manager = IamCache::new(store).await;
2017+
let cache_manager = IamCache::new(store).await.unwrap();
20182018
let iam_sys = IamSys::new(cache_manager);
20192019

20202020
let parent_user = "sts-fallback-test-parent";
@@ -2042,7 +2042,7 @@ mod tests {
20422042
#[tokio::test]
20432043
async fn test_prepare_auth_detects_existing_object_tag_in_session_policy() {
20442044
let store = StsTestMockStore { empty_policies: true };
2045-
let cache_manager = IamCache::new(store).await;
2045+
let cache_manager = IamCache::new(store).await.unwrap();
20462046
let iam_sys = IamSys::new(cache_manager);
20472047
let sts_access_key = "sts-session-tag-test-user";
20482048

@@ -2156,7 +2156,7 @@ mod tests {
21562156
#[tokio::test]
21572157
async fn test_prepare_auth_detects_existing_object_tag_in_encoded_session_policy() {
21582158
let store = StsTestMockStore { empty_policies: true };
2159-
let cache_manager = IamCache::new(store).await;
2159+
let cache_manager = IamCache::new(store).await.unwrap();
21602160
let iam_sys = IamSys::new(cache_manager);
21612161
let sts_access_key = "sts-session-tag-encoded-test-user";
21622162

@@ -2203,7 +2203,7 @@ mod tests {
22032203
#[tokio::test]
22042204
async fn test_prepare_auth_service_account_inherited_ignores_session_policy_tag_hint() {
22052205
let store = StsTestMockStore { empty_policies: false };
2206-
let cache_manager = IamCache::new(store).await;
2206+
let cache_manager = IamCache::new(store).await.unwrap();
22072207
let iam_sys = IamSys::new(cache_manager);
22082208

22092209
let service_account_access_key = "svc-inherited-tag-hint-test-user";
@@ -2266,7 +2266,7 @@ mod tests {
22662266
#[tokio::test]
22672267
async fn test_policy_db_get_skips_nonexistent_groups() {
22682268
let store = StsTestMockStore { empty_policies: false };
2269-
let cache_manager = IamCache::new(store).await;
2269+
let cache_manager = IamCache::new(store).await.unwrap();
22702270
let iam_sys = IamSys::new(cache_manager);
22712271

22722272
// "testgroup" exists with "readwrite" policy; "nonexistent-group" does not exist in IAM.
@@ -2287,7 +2287,7 @@ mod tests {
22872287
#[tokio::test]
22882288
async fn test_info_policy_returns_policy_as_json_object() {
22892289
let store = StsTestMockStore { empty_policies: false };
2290-
let cache_manager = IamCache::new(store).await;
2290+
let cache_manager = IamCache::new(store).await.unwrap();
22912291
let iam_sys = IamSys::new(cache_manager);
22922292

22932293
let policy_info = iam_sys

0 commit comments

Comments
 (0)