@@ -9,10 +9,13 @@ use common::LiveTestContext;
99use common:: reconfigurator:: blueprint_edit_current_target;
1010use futures:: TryStreamExt ;
1111use live_tests_macros:: live_test;
12+ use nexus_client:: types:: BackgroundTasksActivateRequest ;
13+ use nexus_client:: types:: BlueprintTargetSet ;
1214use nexus_client:: types:: Saga ;
1315use nexus_client:: types:: SagaState ;
1416use nexus_inventory:: CollectionBuilder ;
1517use nexus_reconfigurator_planning:: blueprint_builder:: BlueprintBuilder ;
18+ use nexus_reconfigurator_planning:: planner:: Planner ;
1619use nexus_reconfigurator_preparation:: PlanningInputFromDb ;
1720use nexus_sled_agent_shared:: inventory:: ZoneKind ;
1821use nexus_types:: deployment:: SledFilter ;
@@ -156,7 +159,94 @@ async fn test_nexus_add_remove(lc: &LiveTestContext) {
156159 . await
157160 . unwrap ( ) ;
158161
159- // Wait for some other Nexus instance to pick up the saga.
162+ // We want to see another Nexus instance pick up the saga.
163+ //
164+ // For that to happen, inventory must first reflect that the Nexus we
165+ // expunged is really gone. Then we must run through another planning
166+ // round.
167+ //
168+ // First, kick one Nexus instance's inventory collector. Otherwise, it
169+ // might take a while for the system to notice this zone is gone. Having
170+ // activated the task, it shouldn't take too long to get an inventory
171+ info ! ( log, "activating inventory collector" ) ;
172+ nexus
173+ . bgtask_activate ( & BackgroundTasksActivateRequest {
174+ bgtask_names : vec ! [ String :: from( "inventory_collection" ) ] ,
175+ } )
176+ . await
177+ . expect ( "activating inventory background task" ) ;
178+ let latest_collection = wait_for_condition (
179+ || async {
180+ let latest_collection = datastore
181+ . inventory_get_latest_collection ( opctx)
182+ . await
183+ . expect ( "latest inventory collection" )
184+ . expect ( "have a latest inventory collection" ) ;
185+ debug ! ( log, "got inventory" ; "id" => %latest_collection. id) ;
186+ let agent = latest_collection. sled_agents . get ( & sled_id) . expect (
187+ "collection information for the sled we added a Nexus to" ,
188+ ) ;
189+ if agent. omicron_zones . zones . iter ( ) . any ( |z| z. id == new_zone. id ) {
190+ debug ! ( log, "zone still present in inventory" ) ;
191+ return Err ( CondCheckError :: < ( ) > :: NotYet ) ;
192+ }
193+ return Ok ( latest_collection) ;
194+ } ,
195+ & Duration :: from_millis ( 3000 ) ,
196+ & Duration :: from_secs ( 90 ) ,
197+ )
198+ . await
199+ . expect ( "waiting for zone to be gone from inventory" ) ;
200+
201+ // Now run through the planner.
202+ info ! ( log, "running through planner" ) ;
203+ let planning_input = PlanningInputFromDb :: assemble ( & opctx, & datastore)
204+ . await
205+ . expect ( "planning input" ) ;
206+ let ( _, parent_blueprint) = datastore
207+ . blueprint_target_get_current_full ( opctx)
208+ . await
209+ . expect ( "getting latest target blueprint" ) ;
210+ let planner = Planner :: new_based_on (
211+ log. clone ( ) ,
212+ & parent_blueprint,
213+ & planning_input,
214+ "live test suite" ,
215+ & latest_collection,
216+ )
217+ . expect ( "constructing planner" ) ;
218+ let new_blueprint = planner. plan ( ) . expect ( "creating blueprint" ) ;
219+
220+ // The new blueprint ought to have our zone expunged and ready for cleanup.
221+ // We don't need to check this here. It just provides a better error
222+ // message if something has gone wrong up to this point.
223+ let ( _, expunged_zone_config) = new_blueprint
224+ . all_omicron_zones ( |_| true )
225+ . find ( |( _sled_id, zone_config) | zone_config. id == new_zone. id )
226+ . expect ( "expunged zone in new blueprint" ) ;
227+ assert ! ( expunged_zone_config. disposition. is_ready_for_cleanup( ) ) ;
228+
229+ // Now make this the current target.
230+ info ! (
231+ log,
232+ "setting new blueprint target" ;
233+ "blueprint_id" => ?new_blueprint. id
234+ ) ;
235+ nexus
236+ . blueprint_import ( & new_blueprint)
237+ . await
238+ . expect ( "importing new blueprint" ) ;
239+ nexus
240+ . blueprint_target_set ( & BlueprintTargetSet {
241+ enabled : true ,
242+ target_id : new_blueprint. id ,
243+ } )
244+ . await
245+ . expect ( "setting target blueprint" ) ;
246+
247+ // At this point, blueprint execution should re-assign the saga.
248+ // Wait for that to happen and then for another Nexus instance to pick up
249+ // the saga.
160250 let nexus_found = wait_for_condition (
161251 || async {
162252 for nexus_client in & initial_nexus_clients {
@@ -173,8 +263,8 @@ async fn test_nexus_add_remove(lc: &LiveTestContext) {
173263
174264 return Err ( CondCheckError :: < ( ) > :: NotYet ) ;
175265 } ,
176- & Duration :: from_millis ( 50 ) ,
177- & Duration :: from_secs ( 60 ) ,
266+ & Duration :: from_millis ( 1000 ) ,
267+ & Duration :: from_secs ( 120 ) ,
178268 )
179269 . await
180270 . unwrap ( ) ;
0 commit comments