1717
1818import openstack_sync .utils as utils
1919from openstack_sync .hooks import router_flavors as hook
20+ from openstack_sync .hooks .framework import HookInputs
21+ from openstack_sync .hooks .framework import SyncResource
2022from openstack_sync .plugins .neutron .router_flavors import markers
2123from openstack_sync .plugins .neutron .router_flavors .config import BINDING_NAME
2224from openstack_sync .plugins .neutron .router_flavors .config import ENV_PREFIX
@@ -156,13 +158,29 @@ def test_plugin_wait_for_api_uses_configured_retry_budget():
156158 wait .assert_called_once_with (conn , retries = 5 , delay = 0.25 )
157159
158160
159- def test_plugin_prune_is_a_noop_when_disabled ():
161+ def test_plugin_prune_deletes_no_flavor_when_disabled ():
160162 plugin = hook .RouterFlavorPlugin (make_hook_config (prune = False ))
163+ conn = mock .MagicMock ()
161164
162- with mock .patch .object (hook .prune_module , "prune_removed_flavors" ) as prune :
163- plugin .prune (mock .MagicMock (), [{"name" : "a" }], authoritative_empty = False )
165+ with (
166+ mock .patch .object (hook .prune_module , "prune_removed_flavors" ) as prune ,
167+ mock .patch .object (hook .prune_module , "prune_orphaned_profiles" ) as sweep ,
168+ ):
169+ plugin .prune (conn , [{"name" : "a" }], authoritative_empty = False )
164170
165171 prune .assert_not_called ()
172+ sweep .assert_called_once_with (conn )
173+
174+
175+ def test_plugin_prune_sweeps_orphaned_profiles_when_disabled ():
176+ """PRUNE gates flavor deletion; an unattached owned profile is collected anyway."""
177+ plugin = hook .RouterFlavorPlugin (make_hook_config (prune = False ))
178+ conn = mock .MagicMock ()
179+
180+ with mock .patch .object (hook .prune_module , "prune_orphaned_profiles" ) as sweep :
181+ plugin .prune (conn , [{"name" : "a" }], authoritative_empty = False )
182+
183+ sweep .assert_called_once_with (conn )
166184
167185
168186def test_plugin_prune_forwards_authoritative_empty_when_enabled ():
@@ -183,6 +201,61 @@ def test_plugin_cache_is_per_credential_group():
183201 assert plugin .new_cache () is not plugin .new_cache ()
184202
185203
204+ # ---------------------------------------------------------------------------
205+ # Integration through run_sync()
206+ # ---------------------------------------------------------------------------
207+
208+
209+ def test_run_sync_sweeps_orphaned_profile_on_deletion_when_prune_disabled ():
210+ """Deletion-only input must still open a connection for safe orphan cleanup."""
211+ plugin = hook .RouterFlavorPlugin (make_hook_config (prune = False ))
212+ deleted = SyncResource (
213+ spec = {"name" : "pa1410" },
214+ name = "pa1410" ,
215+ namespace = "openstack" ,
216+ generation = 3 ,
217+ secret_name = "infrasetup" ,
218+ cloud_name = "understack" ,
219+ )
220+ conn = _neutron_conn ()
221+ orphan = types .SimpleNamespace (
222+ id = "orphan-profile-id" ,
223+ driver = "neutron_understack.l3_router.vrf.Vrf" ,
224+ is_enabled = True ,
225+ description = "pa1410 profile" ,
226+ meta_info = markers .managed_meta_info ({"vni_alloc" : "auto" }),
227+ )
228+ conn .network .service_profiles .return_value = [
229+ conn .network .service_profiles .return_value [0 ],
230+ orphan ,
231+ ]
232+ inputs = HookInputs (
233+ resources_to_reconcile = [],
234+ desired_resources_for_prune = [],
235+ deleted_resources = [deleted ],
236+ prune_credentials = frozenset ({deleted .credentials }),
237+ unreadable_resources = frozenset (),
238+ )
239+
240+ with (
241+ mock .patch (
242+ "openstack_sync.hooks.framework.get_openstack_connection" ,
243+ return_value = conn ,
244+ ) as connect ,
245+ mock .patch ("openstack_sync.hooks.framework.patch_resource_status" ),
246+ mock .patch .object (hook , "wait_for_openstack_network" ) as wait ,
247+ ):
248+ code = hook .run_sync (plugin , inputs )
249+
250+ assert code == 0
251+ connect .assert_called_once_with ("infrasetup" , "understack" )
252+ wait .assert_called_once_with (conn , retries = 30 , delay = 10.0 )
253+ conn .network .delete_flavor .assert_not_called ()
254+ assert [
255+ call .args [0 ] for call in conn .network .delete_service_profile .call_args_list
256+ ] == [orphan ]
257+
258+
186259# ---------------------------------------------------------------------------
187260# End to end through main()
188261# ---------------------------------------------------------------------------
0 commit comments