diff --git a/ironic/drivers/modules/network/common.py b/ironic/drivers/modules/network/common.py index 9e790d456e..780f5a07e6 100644 --- a/ironic/drivers/modules/network/common.py +++ b/ironic/drivers/modules/network/common.py @@ -594,7 +594,8 @@ class NeutronVIFPortIDMixin(VIFPortIDMixin): # NOTE(vsaienko): allow to unplug VIFs from ACTIVE instance. # NOTE(TheJulia): Also ensure that we delete the vif when in # DELETING state. - if task.node.provision_state in [states.ACTIVE, states.DELETING]: + if task.node.provision_state in [states.ACTIVE, states.DELETING, + states.AVAILABLE]: neutron.unbind_neutron_port(vif_id, context=task.context) def get_node_network_data(self, task): diff --git a/ironic/tests/unit/common/test_network.py b/ironic/tests/unit/common/test_network.py index 0afbe35739..6344252808 100644 --- a/ironic/tests/unit/common/test_network.py +++ b/ironic/tests/unit/common/test_network.py @@ -121,7 +121,9 @@ class TestNetwork(db_base.DbTestCase): def test_get_node_vif_ids_during_rescuing(self): self._test_get_node_vif_ids_multitenancy('rescuing_vif_port_id') - def test_remove_vifs_from_node(self): + @mock.patch.object(neutron_common, 'unbind_neutron_port', + autospec=True) + def test_remove_vifs_from_node(self, mock_unp): db_utils.create_test_port( node_id=self.node.id, address='aa:bb:cc:dd:ee:ff', internal_info={driver_common.TENANT_VIF_KEY: 'test-vif-A'}) @@ -134,6 +136,7 @@ class TestNetwork(db_base.DbTestCase): result = network.get_node_vif_ids(task) self.assertEqual({}, result['ports']) self.assertEqual({}, result['portgroups']) + self.assertTrue(mock_unp.called) class TestRemoveVifsTestCase(db_base.DbTestCase): diff --git a/ironic/tests/unit/drivers/modules/network/test_common.py b/ironic/tests/unit/drivers/modules/network/test_common.py index e4e0d4cd86..13626cec8c 100644 --- a/ironic/tests/unit/drivers/modules/network/test_common.py +++ b/ironic/tests/unit/drivers/modules/network/test_common.py @@ -939,7 +939,7 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.id) as task: self.interface.vif_detach(task, 'fake_vif_id') mock_get.assert_called_once_with(self.interface, task, 'fake_vif_id') - self.assertFalse(mock_unp.called) + self.assertTrue(mock_unp.called) mock_clear.assert_called_once_with(self.port) @mock.patch.object(common.VIFPortIDMixin, '_clear_vif_from_port_like_obj', @@ -954,7 +954,7 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.id) as task: self.interface.vif_detach(task, 'fake_vif_id') mock_get.assert_called_once_with(self.interface, task, 'fake_vif_id') - self.assertFalse(mock_unp.called) + self.assertTrue(mock_unp.called) mock_clear.assert_called_once_with(pg) @mock.patch.object(common.VIFPortIDMixin, '_clear_vif_from_port_like_obj', diff --git a/releasenotes/notes/vif-detach-in-available-acd744acde91fec9.yaml b/releasenotes/notes/vif-detach-in-available-acd744acde91fec9.yaml new file mode 100644 index 0000000000..462b9fd46b --- /dev/null +++ b/releasenotes/notes/vif-detach-in-available-acd744acde91fec9.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fix an issue in the OpenStack Nova-integrated installation that + mac address may conflict at Neutron service during baremetal instance + re-scheduling, by allowing VIFs to be unplugged when a node is in + the AVAILABLE state. See bug + `bug 2109300 `_ + for more details.