@@ -58,6 +58,10 @@ use tracing::{error, info};
5858
5959const IAM_FORMAT_FILE : & str = "format.json" ;
6060const 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 ) ]
6367struct 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 ( ) ;
0 commit comments