@@ -21,11 +21,17 @@ use std::time::Duration;
2121use carbide_switch_controller:: context:: SwitchStateHandlerServices ;
2222use carbide_switch_controller:: handler:: SwitchStateHandler ;
2323use carbide_switch_controller:: io:: SwitchStateControllerIO ;
24+ use carbide_uuid:: rack:: RackId ;
2425use component_manager:: compute_tray_manager:: Backend ;
2526use component_manager:: config:: ComponentManagerConfig ;
27+ use component_manager:: mock:: MockNvSwitchManager ;
28+ use component_manager:: nv_switch_manager:: ConfigureSwitchCertificateJobStatus ;
2629use db:: switch as db_switch;
2730use forge_secrets:: test_support:: credentials:: TestCredentialManager ;
28- use model:: switch:: { ConfiguringState , SwitchControllerState } ;
31+ use model:: component_manager:: ConfigureSwitchCertificateState ;
32+ use model:: switch:: {
33+ ConfigureCertificateState , ConfiguringState , SwitchControllerState , ValidatingState ,
34+ } ;
2935use rpc:: forge:: forge_server:: Forge ;
3036use state_controller:: config:: IterationConfig ;
3137use state_controller:: controller:: StateController ;
@@ -36,7 +42,10 @@ use crate::tests::common::api_fixtures::create_test_env;
3642
3743mod fixtures;
3844mod maintenance;
39- use fixtures:: switch:: { mark_switch_as_deleted, set_switch_controller_state} ;
45+ use fixtures:: switch:: {
46+ configure_certificate_start_state, configure_certificate_wait_state, mark_switch_as_deleted,
47+ set_switch_controller_state, set_switch_rack_id, transition_switch_controller_state,
48+ } ;
4049
4150async fn build_test_component_manager (
4251 env : & common:: api_fixtures:: TestEnv ,
@@ -65,6 +74,284 @@ async fn build_test_component_manager(
6574 . map ( Arc :: new)
6675}
6776
77+ async fn run_switch_controller_with_services (
78+ pool : sqlx:: PgPool ,
79+ work_lock_manager_handle : db:: work_lock_manager:: WorkLockManagerHandle ,
80+ services : SwitchStateHandlerServices ,
81+ ) {
82+ let cancel_token = CancellationToken :: new ( ) ;
83+ let mut controller = StateController :: < SwitchStateControllerIO > :: builder ( )
84+ . iteration_config ( IterationConfig {
85+ iteration_time : Duration :: from_millis ( 50 ) ,
86+ processor_dispatch_interval : Duration :: from_millis ( 10 ) ,
87+ ..Default :: default ( )
88+ } )
89+ . database ( pool, work_lock_manager_handle)
90+ . processor_id ( uuid:: Uuid :: new_v4 ( ) . to_string ( ) )
91+ . services ( services. into ( ) )
92+ . state_handler ( Arc :: new ( SwitchStateHandler :: default ( ) ) )
93+ . build_for_manual_iterations ( cancel_token)
94+ . unwrap ( ) ;
95+ controller. run_single_iteration ( ) . await ;
96+ }
97+
98+ fn mock_component_manager (
99+ nv_switch : Arc < dyn component_manager:: nv_switch_manager:: NvSwitchManager > ,
100+ ) -> Arc < component_manager:: component_manager:: ComponentManager > {
101+ Arc :: new ( component_manager:: component_manager:: ComponentManager :: new (
102+ nv_switch,
103+ Arc :: new ( component_manager:: mock:: MockPowerShelfManager ) ,
104+ Arc :: new ( component_manager:: mock:: MockComputeTrayManager ) ,
105+ false ,
106+ false ,
107+ false ,
108+ ) )
109+ }
110+
111+ #[ crate :: sqlx_test]
112+ async fn test_configure_certificate_start_skips_without_rack_id (
113+ pool : sqlx:: PgPool ,
114+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
115+ let env = create_test_env ( pool. clone ( ) ) . await ;
116+ let switch_id = common:: api_fixtures:: site_explorer:: new_switch ( & env, None , None ) . await ?;
117+
118+ let mut txn = pool. begin ( ) . await ?;
119+ transition_switch_controller_state (
120+ txn. as_mut ( ) ,
121+ & switch_id,
122+ configure_certificate_start_state ( ) ,
123+ )
124+ . await ?;
125+ txn. commit ( ) . await ?;
126+
127+ env. run_switch_controller_iteration ( ) . await ;
128+
129+ let mut txn = pool. acquire ( ) . await ?;
130+ let switch = db_switch:: find_by_id ( & mut txn, & switch_id)
131+ . await ?
132+ . expect ( "switch should exist" ) ;
133+ assert ! ( matches!(
134+ switch. controller_state. value,
135+ SwitchControllerState :: Configuring {
136+ config_state: ConfiguringState :: RotateOsPassword ,
137+ }
138+ ) ) ;
139+ assert ! ( switch. rack_id. is_none( ) ) ;
140+
141+ Ok ( ( ) )
142+ }
143+
144+ #[ crate :: sqlx_test]
145+ async fn test_configure_certificate_start_skips_without_component_manager (
146+ pool : sqlx:: PgPool ,
147+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
148+ let env = create_test_env ( pool. clone ( ) ) . await ;
149+ let switch_id = common:: api_fixtures:: site_explorer:: new_switch ( & env, None , None ) . await ?;
150+ let rack_id = RackId :: new ( uuid:: Uuid :: new_v4 ( ) . to_string ( ) ) ;
151+
152+ let mut txn = pool. begin ( ) . await ?;
153+ set_switch_rack_id ( txn. as_mut ( ) , & switch_id, & rack_id) . await ?;
154+ transition_switch_controller_state (
155+ txn. as_mut ( ) ,
156+ & switch_id,
157+ configure_certificate_start_state ( ) ,
158+ )
159+ . await ?;
160+ txn. commit ( ) . await ?;
161+
162+ run_switch_controller_with_services (
163+ pool. clone ( ) ,
164+ env. api . work_lock_manager_handle . clone ( ) ,
165+ SwitchStateHandlerServices {
166+ db_pool : pool. clone ( ) ,
167+ component_manager : None ,
168+ credential_manager : env. test_credential_manager . clone ( ) ,
169+ } ,
170+ )
171+ . await ;
172+
173+ let mut txn = pool. acquire ( ) . await ?;
174+ let switch = db_switch:: find_by_id ( & mut txn, & switch_id)
175+ . await ?
176+ . expect ( "switch should exist" ) ;
177+ assert ! ( matches!(
178+ switch. controller_state. value,
179+ SwitchControllerState :: Configuring {
180+ config_state: ConfiguringState :: RotateOsPassword ,
181+ }
182+ ) ) ;
183+
184+ Ok ( ( ) )
185+ }
186+
187+ #[ crate :: sqlx_test]
188+ async fn test_configure_certificate_start_transitions_to_wait_for_complete_with_rack_id (
189+ pool : sqlx:: PgPool ,
190+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
191+ let env = create_test_env ( pool. clone ( ) ) . await ;
192+ let switch_id = common:: api_fixtures:: site_explorer:: new_switch ( & env, None , None ) . await ?;
193+ let rack_id = RackId :: new ( uuid:: Uuid :: new_v4 ( ) . to_string ( ) ) ;
194+
195+ let mut txn = pool. begin ( ) . await ?;
196+ set_switch_rack_id ( txn. as_mut ( ) , & switch_id, & rack_id) . await ?;
197+ transition_switch_controller_state (
198+ txn. as_mut ( ) ,
199+ & switch_id,
200+ configure_certificate_start_state ( ) ,
201+ )
202+ . await ?;
203+ txn. commit ( ) . await ?;
204+
205+ run_switch_controller_with_services (
206+ pool. clone ( ) ,
207+ env. api . work_lock_manager_handle . clone ( ) ,
208+ SwitchStateHandlerServices {
209+ db_pool : pool. clone ( ) ,
210+ component_manager : Some ( mock_component_manager ( Arc :: new ( MockNvSwitchManager :: default ( ) ) ) ) ,
211+ credential_manager : env. test_credential_manager . clone ( ) ,
212+ } ,
213+ )
214+ . await ;
215+
216+ let mut txn = pool. acquire ( ) . await ?;
217+ let switch = db_switch:: find_by_id ( & mut txn, & switch_id)
218+ . await ?
219+ . expect ( "switch should exist" ) ;
220+ assert ! ( matches!(
221+ switch. controller_state. value,
222+ SwitchControllerState :: Configuring {
223+ config_state: ConfiguringState :: ConfigureCertificate {
224+ configure_certificate: ConfigureCertificateState :: WaitForComplete {
225+ ref job_id
226+ } ,
227+ } ,
228+ } if job_id == "mock-switch-cert-job"
229+ ) ) ;
230+ assert_eq ! ( switch. rack_id. as_ref( ) , Some ( & rack_id) ) ;
231+
232+ Ok ( ( ) )
233+ }
234+
235+ #[ crate :: sqlx_test]
236+ async fn test_configure_certificate_wait_for_complete_transitions_to_rotate_os_password (
237+ pool : sqlx:: PgPool ,
238+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
239+ let env = create_test_env ( pool. clone ( ) ) . await ;
240+ let switch_id = common:: api_fixtures:: site_explorer:: new_switch ( & env, None , None ) . await ?;
241+
242+ let mut txn = pool. begin ( ) . await ?;
243+ transition_switch_controller_state (
244+ txn. as_mut ( ) ,
245+ & switch_id,
246+ configure_certificate_wait_state ( "mock-switch-cert-job" ) ,
247+ )
248+ . await ?;
249+ txn. commit ( ) . await ?;
250+
251+ run_switch_controller_with_services (
252+ pool. clone ( ) ,
253+ env. api . work_lock_manager_handle . clone ( ) ,
254+ SwitchStateHandlerServices {
255+ db_pool : pool. clone ( ) ,
256+ component_manager : Some ( mock_component_manager ( Arc :: new ( MockNvSwitchManager :: default ( ) ) ) ) ,
257+ credential_manager : env. test_credential_manager . clone ( ) ,
258+ } ,
259+ )
260+ . await ;
261+
262+ let mut txn = pool. acquire ( ) . await ?;
263+ let switch = db_switch:: find_by_id ( & mut txn, & switch_id)
264+ . await ?
265+ . expect ( "switch should exist" ) ;
266+ assert ! ( matches!(
267+ switch. controller_state. value,
268+ SwitchControllerState :: Configuring {
269+ config_state: ConfiguringState :: RotateOsPassword ,
270+ }
271+ ) ) ;
272+
273+ Ok ( ( ) )
274+ }
275+
276+ #[ crate :: sqlx_test]
277+ async fn test_configure_certificate_wait_for_complete_transitions_to_error_on_failure (
278+ pool : sqlx:: PgPool ,
279+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
280+ let env = create_test_env ( pool. clone ( ) ) . await ;
281+ let switch_id = common:: api_fixtures:: site_explorer:: new_switch ( & env, None , None ) . await ?;
282+
283+ let mut txn = pool. begin ( ) . await ?;
284+ transition_switch_controller_state (
285+ txn. as_mut ( ) ,
286+ & switch_id,
287+ configure_certificate_wait_state ( "mock-switch-cert-job" ) ,
288+ )
289+ . await ?;
290+ txn. commit ( ) . await ?;
291+
292+ let failing_mock = MockNvSwitchManager :: default ( ) . with_certificate_job_status (
293+ ConfigureSwitchCertificateJobStatus {
294+ state : ConfigureSwitchCertificateState :: Failed ,
295+ error : Some ( "cert install failed" . to_string ( ) ) ,
296+ } ,
297+ ) ;
298+ run_switch_controller_with_services (
299+ pool. clone ( ) ,
300+ env. api . work_lock_manager_handle . clone ( ) ,
301+ SwitchStateHandlerServices {
302+ db_pool : pool. clone ( ) ,
303+ component_manager : Some ( mock_component_manager ( Arc :: new ( failing_mock) ) ) ,
304+ credential_manager : env. test_credential_manager . clone ( ) ,
305+ } ,
306+ )
307+ . await ;
308+
309+ let mut txn = pool. acquire ( ) . await ?;
310+ let switch = db_switch:: find_by_id ( & mut txn, & switch_id)
311+ . await ?
312+ . expect ( "switch should exist" ) ;
313+ assert ! ( matches!(
314+ switch. controller_state. value,
315+ SwitchControllerState :: Error { ref cause } if cause == "cert install failed"
316+ ) ) ;
317+
318+ Ok ( ( ) )
319+ }
320+
321+ #[ crate :: sqlx_test]
322+ async fn test_rotate_os_password_transitions_to_validating (
323+ pool : sqlx:: PgPool ,
324+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
325+ let env = create_test_env ( pool. clone ( ) ) . await ;
326+ let switch_id = common:: api_fixtures:: site_explorer:: new_switch ( & env, None , None ) . await ?;
327+
328+ let mut txn = pool. begin ( ) . await ?;
329+ transition_switch_controller_state (
330+ txn. as_mut ( ) ,
331+ & switch_id,
332+ SwitchControllerState :: Configuring {
333+ config_state : ConfiguringState :: RotateOsPassword ,
334+ } ,
335+ )
336+ . await ?;
337+ txn. commit ( ) . await ?;
338+
339+ env. run_switch_controller_iteration ( ) . await ;
340+
341+ let mut txn = pool. acquire ( ) . await ?;
342+ let switch = db_switch:: find_by_id ( & mut txn, & switch_id)
343+ . await ?
344+ . expect ( "switch should exist" ) ;
345+ assert ! ( matches!(
346+ switch. controller_state. value,
347+ SwitchControllerState :: Validating {
348+ validating_state: ValidatingState :: ValidationComplete ,
349+ }
350+ ) ) ;
351+
352+ Ok ( ( ) )
353+ }
354+
68355#[ crate :: sqlx_test]
69356async fn test_switch_state_transition_validation (
70357 pool : sqlx:: PgPool ,
@@ -194,9 +481,9 @@ async fn test_switch_deletion_with_state_controller(
194481}
195482
196483/// Tests the entire Switch ControllerState transition flow: Initializing -> Configuring
197- /// (RotateOsPassword ) -> Validating (ValidationComplete ) -> BomValidating
198- /// (BomValidationComplete) -> Ready. Uses the real SwitchStateHandler so each state handler
199- /// performs its transition.
484+ /// (ConfigureCertificate ) -> Configuring (RotateOsPassword ) -> Validating (ValidationComplete)
485+ /// -> BomValidating (BomValidationComplete) -> Ready. Uses the real SwitchStateHandler so each
486+ /// state handler performs its transition.
200487#[ crate :: sqlx_test]
201488async fn test_switch_entire_state_transition_flow (
202489 pool : sqlx:: PgPool ,
0 commit comments