@@ -1746,6 +1746,91 @@ def test_destroy(self):
17461746 for state in ironic_states .PROVISION_STATE_LIST :
17471747 self ._test_destroy (state )
17481748
1749+ @mock .patch .object (ironic_driver .IronicDriver ,
1750+ '_remove_instance_info_from_node' )
1751+ @mock .patch .object (ironic_driver .IronicDriver , '_cleanup_deploy' )
1752+ def test_destroy_servicing_states_unprovision (self , mock_cleanup_deploy ,
1753+ mock_remove_instance_info ):
1754+ """Test that servicing and deployhold states trigger unprovisioning.
1755+
1756+ This is a regression test for bug 2131960 where nodes in servicing
1757+ states (SERVICING, SERVICEWAIT, SERVICEHOLD, SERVICEFAIL) and
1758+ DEPLOYHOLD were not being properly unprovisioned when destroyed.
1759+ """
1760+ # Test all servicing-related states and deployhold
1761+ servicing_states = [
1762+ ironic_states .DEPLOYHOLD ,
1763+ ironic_states .SERVICING ,
1764+ ironic_states .SERVICEWAIT ,
1765+ ironic_states .SERVICEFAIL ,
1766+ ironic_states .SERVICEHOLD ,
1767+ ]
1768+
1769+ for state in servicing_states :
1770+ mock_cleanup_deploy .reset_mock ()
1771+ mock_remove_instance_info .reset_mock ()
1772+ self .mock_conn .set_node_provision_state .reset_mock ()
1773+
1774+ node_id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
1775+ network_info = 'foo'
1776+
1777+ node = _get_cached_node (
1778+ driver = 'fake' , id = node_id , provision_state = state )
1779+ instance = fake_instance .fake_instance_obj (self .ctx , node = node_id )
1780+
1781+ def fake_set_node_provision_state (* _ ):
1782+ node .provision_state = None
1783+
1784+ self .mock_conn .nodes .return_value = iter ([node ])
1785+ self .mock_conn .set_node_provision_state .side_effect = \
1786+ fake_set_node_provision_state
1787+ self .driver .destroy (self .ctx , instance , network_info , None )
1788+
1789+ # All these states should trigger unprovisioning, not cleanup
1790+ self .mock_conn .set_node_provision_state .assert_called_once_with (
1791+ node_id , 'deleted' ,
1792+ )
1793+ self .assertFalse (mock_remove_instance_info .called )
1794+ self .assertFalse (mock_cleanup_deploy .called )
1795+
1796+ @mock .patch .object (ironic_driver .IronicDriver ,
1797+ '_remove_instance_info_from_node' )
1798+ @mock .patch .object (ironic_driver .IronicDriver , '_cleanup_deploy' )
1799+ def test_destroy_unknown_state_unprovision (self , mock_cleanup_deploy ,
1800+ mock_remove_instance_info ):
1801+ """Test that unknown provision states trigger unprovisioning.
1802+
1803+ This is a regression test for bug 2131960. As a safety mechanism,
1804+ nodes in provision states that are not recognized (not in
1805+ PROVISION_STATE_LIST) should trigger unprovisioning rather than
1806+ cleanup to ensure proper node teardown even when encountering
1807+ unexpected or future Ironic states.
1808+ """
1809+ # Test with a completely unknown state that doesn't exist in
1810+ # PROVISION_STATE_LIST
1811+ node_id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
1812+ network_info = 'foo'
1813+ unknown_state = 'unknown-future-state'
1814+
1815+ node = _get_cached_node (
1816+ driver = 'fake' , id = node_id , provision_state = unknown_state )
1817+ instance = fake_instance .fake_instance_obj (self .ctx , node = node_id )
1818+
1819+ def fake_set_node_provision_state (* _ ):
1820+ node .provision_state = None
1821+
1822+ self .mock_conn .nodes .return_value = iter ([node ])
1823+ self .mock_conn .set_node_provision_state .side_effect = \
1824+ fake_set_node_provision_state
1825+ self .driver .destroy (self .ctx , instance , network_info , None )
1826+
1827+ # Unknown states should trigger unprovisioning for safety
1828+ self .mock_conn .set_node_provision_state .assert_called_once_with (
1829+ node_id , 'deleted' ,
1830+ )
1831+ self .assertFalse (mock_remove_instance_info .called )
1832+ self .assertFalse (mock_cleanup_deploy .called )
1833+
17491834 @mock .patch .object (ironic_driver .IronicDriver ,
17501835 '_validate_instance_and_node' )
17511836 @mock .patch .object (ironic_driver .IronicDriver ,
0 commit comments