Skip to content

Commit d2f3499

Browse files
committed
Accept a port deletion with missing port binding information
If the database "port" child register "ml2_port_bindings" has been manually deleted from the database, now is possible to delete the Neutron Port resource (that implies the "port" database register and all other child registers) Before this patch, the port deletion CLI command succeeded (no exception was raised) but the Port resource was not deleted. Closes-Bug: #1988323 Conflicts: neutron/plugins/ml2/plugin.py Change-Id: I02de276d0cd8e4ae27355d4aee5f48e92634f318 (cherry picked from commit 2f0919f)
1 parent a06dab2 commit d2f3499

File tree

3 files changed

+80
-28
lines changed

3 files changed

+80
-28
lines changed

neutron/db/l3_dvrscheduler_db.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,10 @@ def get_dvr_routers_to_remove(self, context, deleted_port,
206206
return []
207207

208208
admin_context = context.elevated()
209-
port_host = deleted_port[portbindings.HOST_ID]
209+
port_host = deleted_port.get(portbindings.HOST_ID)
210+
if not port_host:
211+
return []
212+
210213
subnet_ids = [ip['subnet_id'] for ip in deleted_port['fixed_ips']]
211214
router_ids = self.get_dvr_routers_by_subnet_ids(admin_context,
212215
subnet_ids)

neutron/plugins/ml2/plugin.py

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,6 +2048,26 @@ def _pre_delete_port(self, context, port_id, port_check, port=None):
20482048
@utils.transaction_guard
20492049
@db_api.retry_if_session_inactive()
20502050
def delete_port(self, context, id, l3_port_check=True):
2051+
2052+
def handle_distributed_port_bindings(mech_contexts):
2053+
bindings = db.get_distributed_port_bindings(context, id)
2054+
for bind in bindings:
2055+
levels = db.get_binding_level_objs(context, id, bind.host)
2056+
metadata['bind'] = bind
2057+
metadata['levels'] = levels
2058+
registry.publish(resources.PORT,
2059+
events.PRECOMMIT_DELETE,
2060+
self,
2061+
payload=events.DBEventPayload(
2062+
context,
2063+
resource_id=id,
2064+
metadata=metadata,
2065+
states=(port,)))
2066+
mech_context = driver_context.PortContext(
2067+
self, context, port, network, bind, levels)
2068+
self.mechanism_manager.delete_port_precommit(mech_context)
2069+
mech_contexts.append(mech_context)
2070+
20512071
try:
20522072
port_db = self._get_port(context, id)
20532073
port = self._make_port_dict(port_db)
@@ -2062,48 +2082,38 @@ def delete_port(self, context, id, l3_port_check=True):
20622082

20632083
with db_api.CONTEXT_WRITER.using(context):
20642084
binding = p_utils.get_port_binding_by_status_and_host(
2065-
port_db.port_bindings, const.ACTIVE,
2066-
raise_if_not_found=True, port_id=id)
2085+
port_db.port_bindings, const.ACTIVE, port_id=id)
20672086

20682087
network = self.get_network(context, port['network_id'])
20692088
bound_mech_contexts = []
20702089
device_owner = port['device_owner']
20712090
metadata = {'network': network,
20722091
'port_db': port_db,
20732092
'bindings': binding}
2074-
if device_owner == const.DEVICE_OWNER_DVR_INTERFACE:
2075-
bindings = db.get_distributed_port_bindings(context,
2076-
id)
2077-
for bind in bindings:
2078-
levels = db.get_binding_level_objs(context, id, bind.host)
2079-
metadata['bind'] = bind
2093+
2094+
if not binding:
2095+
LOG.warning('The port %s has no binding information, the '
2096+
'"ml2_port_bindings" register is not present', id)
2097+
if device_owner == const.DEVICE_OWNER_DVR_INTERFACE:
2098+
handle_distributed_port_bindings(bound_mech_contexts)
2099+
else:
2100+
if device_owner == const.DEVICE_OWNER_DVR_INTERFACE:
2101+
handle_distributed_port_bindings(bound_mech_contexts)
2102+
else:
2103+
levels = db.get_binding_level_objs(context, id,
2104+
binding.host)
2105+
metadata['bind'] = None
20802106
metadata['levels'] = levels
2081-
registry.publish(resources.PORT,
2082-
events.PRECOMMIT_DELETE,
2083-
self,
2084-
payload=events.DBEventPayload(
2107+
registry.publish(resources.PORT, events.PRECOMMIT_DELETE,
2108+
self, payload=events.DBEventPayload(
20852109
context,
20862110
resource_id=id,
20872111
metadata=metadata,
20882112
states=(port,)))
20892113
mech_context = driver_context.PortContext(
2090-
self, context, port, network, bind, levels)
2114+
self, context, port, network, binding, levels)
20912115
self.mechanism_manager.delete_port_precommit(mech_context)
20922116
bound_mech_contexts.append(mech_context)
2093-
else:
2094-
levels = db.get_binding_level_objs(context, id, binding.host)
2095-
metadata['bind'] = None
2096-
metadata['levels'] = levels
2097-
registry.publish(resources.PORT, events.PRECOMMIT_DELETE, self,
2098-
payload=events.DBEventPayload(
2099-
context,
2100-
resource_id=id,
2101-
metadata=metadata,
2102-
states=(port,)))
2103-
mech_context = driver_context.PortContext(
2104-
self, context, port, network, binding, levels)
2105-
self.mechanism_manager.delete_port_precommit(mech_context)
2106-
bound_mech_contexts.append(mech_context)
21072117
if l3plugin:
21082118
router_ids = l3plugin.disassociate_floatingips(
21092119
context, id, do_notify=False)

neutron/tests/functional/plugins/ml2/test_plugin.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,16 @@
1313
# License for the specific language governing permissions and limitations
1414
# under the License.
1515

16+
from unittest import mock
17+
1618
from neutron_lib.api.definitions import portbindings
1719
from neutron_lib import constants
1820
from neutron_lib import context
21+
from neutron_lib.db import api as db_api
1922

2023
from neutron.db import agents_db
24+
from neutron.plugins.ml2 import models
25+
from neutron.plugins.ml2 import plugin as ml2_plugin
2126
from neutron.tests.common import helpers
2227
from neutron.tests.unit.plugins.ml2 import base as ml2_test_base
2328

@@ -32,6 +37,12 @@ def setUp(self):
3237
self.admin_context = context.get_admin_context()
3338
self.host_args = {portbindings.HOST_ID: helpers.HOST,
3439
'admin_state_up': True}
40+
self._max_bind_retries = ml2_plugin.MAX_BIND_TRIES
41+
ml2_plugin.MAX_BIND_TRIES = 1
42+
self.addCleanup(self._restore_max_bind_retries)
43+
44+
def _restore_max_bind_retries(self):
45+
ml2_plugin.MAX_BIND_TRIES = self._max_bind_retries
3546

3647
def test_port_bind_successfully(self):
3748
helpers.register_ovs_agent(host=helpers.HOST)
@@ -70,3 +81,31 @@ def test_port_bind_retry(self):
7081
portbindings.VIF_TYPE_OVS)
7182
self.assertEqual(bound_context.current['binding:vif_type'],
7283
portbindings.VIF_TYPE_OVS)
84+
85+
@mock.patch.object(ml2_plugin, 'LOG')
86+
def test_delete_port_no_binding_register(self, mock_log):
87+
with self.network() as network:
88+
with self.subnet(network=network) as subnet:
89+
with self.port(
90+
subnet=subnet, device_owner=DEVICE_OWNER_COMPUTE,
91+
arg_list=(portbindings.HOST_ID, 'admin_state_up',),
92+
**self.host_args) as port:
93+
pass
94+
95+
port_id = port['port']['id']
96+
ports = self._list('ports')['ports']
97+
self.assertEqual(1, len(ports))
98+
self.assertEqual(port_id, ports[0]['id'])
99+
with db_api.CONTEXT_WRITER.using(self.context):
100+
port_binding = self.context.session.query(
101+
models.PortBinding).filter(
102+
models.PortBinding.port_id == port_id).one()
103+
self.context.session.delete(port_binding)
104+
105+
req = self.new_delete_request('ports', port['port']['id'])
106+
req.get_response(self.api)
107+
ports = self._list('ports')['ports']
108+
self.assertEqual(0, len(ports))
109+
mock_log.warning.assert_called_once_with(
110+
'The port %s has no binding information, the "ml2_port_bindings" '
111+
'register is not present', port_id)

0 commit comments

Comments
 (0)