Skip to content

Commit d890ad3

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Accept a port deletion with missing port binding information" into stable/yoga
2 parents 89eb9e4 + d2f3499 commit d890ad3

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)