diff --git a/quantum/db/l3_db.py b/quantum/db/l3_db.py index 506060fcbc..514d7a691d 100644 --- a/quantum/db/l3_db.py +++ b/quantum/db/l3_db.py @@ -740,8 +740,17 @@ class L3_NAT_db_mixin(l3.RouterPluginBase): if port_db['device_owner'] in [DEVICE_OWNER_ROUTER_INTF, DEVICE_OWNER_ROUTER_GW, DEVICE_OWNER_FLOATINGIP]: - raise l3.L3PortInUse(port_id=port_id, - device_owner=port_db['device_owner']) + # Raise port in use only if the port has IP addresses + # Otherwise it's a stale port that can be removed + fixed_ips = port_db['fixed_ips'].all() + if fixed_ips: + raise l3.L3PortInUse(port_id=port_id, + device_owner=port_db['device_owner']) + else: + LOG.debug(_("Port %(port_id)s has owner %(port_owner)s, but " + "no IP address, so it can be deleted"), + {'port_id': port_db['id'], + 'port_owner': port_db['device_owner']}) def disassociate_floatingips(self, context, port_id): with context.session.begin(subtransactions=True): diff --git a/quantum/tests/unit/test_l3_plugin.py b/quantum/tests/unit/test_l3_plugin.py index 8d7414d672..509ba8fc58 100644 --- a/quantum/tests/unit/test_l3_plugin.py +++ b/quantum/tests/unit/test_l3_plugin.py @@ -968,6 +968,23 @@ class L3NatDBTestCase(L3NatTestCaseBase): r['router']['id'], n['network']['id'], expected_code=exc.HTTPBadRequest.code) + def test_delete_unused_router_interface(self): + with self.network() as n: + with self.router() as r: + with self.subnet(network=n) as s: + res = self._create_port(self.fmt, + s['subnet']['network_id']) + p = self.deserialize(self.fmt, res) + self._router_interface_action('add', + r['router']['id'], + None, + p['port']['id']) + # The subnet here is deleted, and the port should have no IP + self._delete('ports', p['port']['id']) + # Verify the port has been deleted + self._show('ports', p['port']['id'], + expected_code=exc.HTTPNotFound.code) + def test_router_remove_interface_inuse_returns_409(self): with self.router() as r: with self.subnet() as s: