diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py index 5ddab21e4e..1610ca3986 100644 --- a/ironic/conductor/manager.py +++ b/ironic/conductor/manager.py @@ -2143,7 +2143,8 @@ class ConductorManager(base_manager.BaseConductorManager): @METRICS.timer('ConductorManager.destroy_port') @messaging.expected_exceptions(exception.NodeLocked, - exception.NodeNotFound) + exception.NodeNotFound, + exception.InvalidState) def destroy_port(self, context, port): """Delete a port. @@ -2158,6 +2159,14 @@ class ConductorManager(base_manager.BaseConductorManager): {'port': port.uuid}) with task_manager.acquire(context, port.node_id, purpose='port deletion') as task: + node = task.node + if ((node.provision_state == states.ACTIVE or node.instance_uuid) + and not node.maintenance): + msg = _("Cannot delete the port %(port)s as node " + "%(node)s is active or has " + "instance UUID assigned") + raise exception.InvalidState(msg % {'node': node.uuid, + 'port': port.uuid}) port.destroy() LOG.info('Successfully deleted port %(port)s. ' 'The node associated with the port was %(node)s', diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index a826e342d7..72152aef89 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -7180,6 +7180,45 @@ class DestroyPortTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): # Compare true exception hidden by @messaging.expected_exceptions self.assertEqual(exception.NodeLocked, exc.exc_info[0]) + def test_destroy_port_node_active_state(self): + instance_uuid = uuidutils.generate_uuid() + node = obj_utils.create_test_node(self.context, driver='fake-hardware', + instance_uuid=instance_uuid, + provision_state='active') + port = obj_utils.create_test_port(self.context, + node_id=node.id, + extra={'vif_port_id': 'fake-id'}) + exc = self.assertRaises(messaging.rpc.ExpectedException, + self.service.destroy_port, + self.context, port) + self.assertEqual(exception.InvalidState, exc.exc_info[0]) + + def test_destroy_port_node_active_and_maintenance(self): + instance_uuid = uuidutils.generate_uuid() + node = obj_utils.create_test_node(self.context, driver='fake-hardware', + instance_uuid=instance_uuid, + provision_state='active', + maintenance=True) + port = obj_utils.create_test_port(self.context, + node_id=node.id, + extra={'vif_port_id': 'fake-id'}) + self.service.destroy_port(self.context, port) + self.assertRaises(exception.PortNotFound, + self.dbapi.get_port_by_uuid, + port.uuid) + + def test_destroy_port_with_instance_not_in_active(self): + instance_uuid = uuidutils.generate_uuid() + node = obj_utils.create_test_node(self.context, driver='fake-hardware', + instance_uuid=instance_uuid, + provision_state='deploy failed') + port = obj_utils.create_test_port(self.context, + node_id=node.id) + exc = self.assertRaises(messaging.rpc.ExpectedException, + self.service.destroy_port, + self.context, port) + self.assertEqual(exception.InvalidState, exc.exc_info[0]) + @mgr_utils.mock_record_keepalive class DestroyPortgroupTestCase(mgr_utils.ServiceSetUpMixin, diff --git a/releasenotes/notes/port_delete-6628b736a1b556f6.yaml b/releasenotes/notes/port_delete-6628b736a1b556f6.yaml new file mode 100644 index 0000000000..757c97d476 --- /dev/null +++ b/releasenotes/notes/port_delete-6628b736a1b556f6.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Prevents deletion of ports for active nodes. It is still possible to + delete them after putting the node in the maintenance mode.