@@ -10309,6 +10309,20 @@ def test_multi_app_independent(self, mock_deployment_state_manager):
1030910309 assert set (tiers [0 ]) == {a1 , a2 }
1031010310 assert set (tiers [1 ]) == {b1 , b2 }
1031110311
10312+ def test_cross_app_chain (self , mock_deployment_state_manager ):
10313+ """A caller in app1 orders before its callee in app2."""
10314+ create_dsm , _ , _ , _ = mock_deployment_state_manager
10315+ dsm : DeploymentStateManager = create_dsm ()
10316+ ingress1 = _dep ("ingress" , app = "app1" )
10317+ ingress2 = _dep ("ingress" , app = "app2" )
10318+ leaf2 = _dep ("leaf" , app = "app2" )
10319+ _deploy_running (dsm , ingress1 , outbound = [ingress2 ])
10320+ _deploy_running (dsm , ingress2 , outbound = [leaf2 ])
10321+ _deploy_running (dsm , leaf2 , outbound = [])
10322+
10323+ tiers = dsm ._shutdown_deletion_tiers ()
10324+ assert tiers == [[ingress1 ], [ingress2 ], [leaf2 ]]
10325+
1031210326 def test_no_deployments (self , mock_deployment_state_manager ):
1031310327 """No deployments produces no tiers."""
1031410328 create_dsm , _ , _ , _ = mock_deployment_state_manager
@@ -10396,6 +10410,21 @@ def test_multi_app_independent(self, mock_deployment_state_manager):
1039610410 assert order .index (b1 ) > order .index (a1 )
1039710411 assert order .index (b2 ) > order .index (a2 )
1039810412
10413+ def test_cross_app_chain_delete_order (self , mock_deployment_state_manager ):
10414+ """App 1 calling App 2 tears the app1 caller down before the app2 callee."""
10415+ create_dsm , _ , _ , _ = mock_deployment_state_manager
10416+ dsm : DeploymentStateManager = create_dsm ()
10417+ ingress1 = _dep ("ingress" , app = "app1" )
10418+ ingress2 = _dep ("ingress" , app = "app2" )
10419+ leaf2 = _dep ("leaf" , app = "app2" )
10420+ _deploy_running (dsm , ingress1 , outbound = [ingress2 ])
10421+ _deploy_running (dsm , ingress2 , outbound = [leaf2 ])
10422+ _deploy_running (dsm , leaf2 , outbound = [])
10423+
10424+ order = _run_shutdown_to_completion (dsm )
10425+ assert dsm .is_ready_for_shutdown ()
10426+ assert order == [ingress1 , ingress2 , leaf2 ]
10427+
1039910428 def test_wedged_tier_still_completes (self , mock_deployment_state_manager ):
1040010429 """A tier that never drains is force-advanced past after the timeout."""
1040110430 create_dsm , timer , _ , _ = mock_deployment_state_manager
@@ -10488,5 +10517,73 @@ def test_shutdown_tiers_survive_application_reconcile(mock_deployment_state_mana
1048810517 assert dsm .is_ready_for_shutdown ()
1048910518
1049010519
10520+ def test_cross_app_shutdown_survives_application_reconcile (
10521+ mock_deployment_state_manager ,
10522+ ):
10523+ """Cross app tier order holds when each app delete wipes its target list."""
10524+ create_dsm , _ , _ , autoscaling_state_manager = mock_deployment_state_manager
10525+ dsm : DeploymentStateManager = create_dsm ()
10526+
10527+ def _make_app (name ):
10528+ return ApplicationState (
10529+ name = name ,
10530+ deployment_state_manager = dsm ,
10531+ autoscaling_state_manager = autoscaling_state_manager ,
10532+ endpoint_state = EndpointState (MockKVStore (), Mock ()),
10533+ logging_config = LoggingConfig (),
10534+ external_scaler_enabled = False ,
10535+ )
10536+
10537+ app1 = _make_app ("app1" )
10538+ app2 = _make_app ("app2" )
10539+ info1 , _ = deployment_info (num_replicas = 1 )
10540+ info2 , _ = deployment_info (num_replicas = 1 )
10541+ app1 .deploy_app ({"ingress" : info1 }, external_scaler_enabled = False )
10542+ app2 .deploy_app ({"backend" : info2 }, external_scaler_enabled = False )
10543+ app1 .update ()
10544+ app2 .update ()
10545+
10546+ ingress = _dep ("ingress" , app = "app1" )
10547+ backend = _dep ("backend" , app = "app2" )
10548+ ds_ingress = dsm ._get_deployment_state_for_testing (ingress )
10549+ ds_backend = dsm ._get_deployment_state_for_testing (backend )
10550+
10551+ dsm .update ()
10552+ for r in ds_ingress ._replicas .get ([ReplicaState .STARTING ]):
10553+ r ._actor .set_ready ()
10554+ for r in ds_backend ._replicas .get ([ReplicaState .STARTING ]):
10555+ r ._actor .set_ready ()
10556+ dsm .update ()
10557+ for r in ds_ingress ._replicas .get ([ReplicaState .RUNNING ]):
10558+ r ._actor ._outbound_deployments = [backend ]
10559+ for r in ds_backend ._replicas .get ([ReplicaState .RUNNING ]):
10560+ r ._actor ._outbound_deployments = []
10561+
10562+ dsm .shutdown ()
10563+ assert ds_ingress ._target_state .deleting
10564+ assert not ds_backend ._target_state .deleting
10565+
10566+ app1 .delete ()
10567+ app2 .delete ()
10568+ app1 .update ()
10569+ app2 .update ()
10570+ assert not ds_backend ._target_state .deleting
10571+
10572+ dsm .update ()
10573+ for r in ds_ingress ._replicas .get ([ReplicaState .STOPPING ]):
10574+ r ._actor .set_done_stopping ()
10575+ dsm .update ()
10576+ dsm .shutdown ()
10577+ app1 .update ()
10578+ app2 .update ()
10579+ assert ds_backend ._target_state .deleting
10580+
10581+ dsm .update ()
10582+ for r in ds_backend ._replicas .get ([ReplicaState .STOPPING ]):
10583+ r ._actor .set_done_stopping ()
10584+ dsm .update ()
10585+ assert dsm .is_ready_for_shutdown ()
10586+
10587+
1049110588if __name__ == "__main__" :
1049210589 sys .exit (pytest .main (["-v" , "-s" , __file__ ]))
0 commit comments