Skip to content

Commit 539961b

Browse files
committed
[OVN] Prevent Trunk creation/deletion with parent port bound
This patch imitates the ML2/OVS Trunk driver behaviour. When the trunk parent port is bound: * A new trunk cannot be created using this parent port. * If the port is assigned as parent port of a trunk, this trunk cannot be deleted. Conflicts: neutron/db/l3_dvr_db.py neutron/common/utils.py Closes-Bug: #2022059 Change-Id: I8cfa7e67524a42224cbb4b3c3cec3cfa49b795fd (cherry picked from commit 833a6d8) (cherry picked from commit 2f48c24) (cherry picked from commit 0d49980)
1 parent 73ba302 commit 539961b

File tree

8 files changed

+79
-19
lines changed

8 files changed

+79
-19
lines changed

neutron/common/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@
3535
from eventlet.green import subprocess
3636
import netaddr
3737
from neutron_lib.api.definitions import availability_zone as az_def
38+
from neutron_lib.api.definitions import portbindings
39+
from neutron_lib.api.definitions import portbindings_extended
3840
from neutron_lib import constants as n_const
3941
from neutron_lib import context as n_context
4042
from neutron_lib.db import api as db_api
43+
from neutron_lib.plugins import utils as plugin_utils
4144
from neutron_lib.services.trunk import constants as trunk_constants
4245
from neutron_lib.utils import helpers
4346
from oslo_config import cfg
@@ -1046,3 +1049,16 @@ def is_session_active(session):
10461049
if not (session.dirty or session.deleted or session.new):
10471050
return False
10481051
return True
1052+
1053+
1054+
# TODO(slaweq): this should be moved to neutron_lib.plugins.utils module
1055+
def is_port_bound(port, log_message=True):
1056+
active_binding = plugin_utils.get_port_binding_by_status_and_host(
1057+
port.get('port_bindings', []), n_const.ACTIVE)
1058+
if not active_binding:
1059+
if log_message:
1060+
LOG.warning('Binding for port %s was not found.', port)
1061+
return False
1062+
return active_binding[portbindings_extended.VIF_TYPE] not in (
1063+
portbindings.VIF_TYPE_UNBOUND,
1064+
portbindings.VIF_TYPE_BINDING_FAILED)

neutron/db/l3_dvr_db.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from neutron_lib.api.definitions import external_net as extnet_apidef
1818
from neutron_lib.api.definitions import l3 as l3_apidef
1919
from neutron_lib.api.definitions import portbindings
20-
from neutron_lib.api.definitions import portbindings_extended
2120
from neutron_lib.api.definitions import router_admin_state_down_before_update
2221
from neutron_lib.api import validators
2322
from neutron_lib.callbacks import events
@@ -70,18 +69,6 @@ def is_admin_state_down_necessary():
7069
return _IS_ADMIN_STATE_DOWN_NECESSARY
7170

7271

73-
# TODO(slaweq): this should be moved to neutron_lib.plugins.utils module
74-
def is_port_bound(port):
75-
active_binding = plugin_utils.get_port_binding_by_status_and_host(
76-
port.get("port_bindings", []), const.ACTIVE)
77-
if not active_binding:
78-
LOG.warning("Binding for port %s was not found.", port)
79-
return False
80-
return active_binding[portbindings_extended.VIF_TYPE] not in [
81-
portbindings.VIF_TYPE_UNBOUND,
82-
portbindings.VIF_TYPE_BINDING_FAILED]
83-
84-
8572
@registry.has_registry_receivers
8673
class DVRResourceOperationHandler(object):
8774
"""Contains callbacks for DVR operations.
@@ -1426,7 +1413,7 @@ def is_router_distributed(self, context, router_id):
14261413

14271414
def get_ports_under_dvr_connected_subnet(self, context, subnet_id):
14281415
query = dvr_mac_db.get_ports_query_by_subnet_and_ip(context, subnet_id)
1429-
ports = [p for p in query.all() if is_port_bound(p)]
1416+
ports = [p for p in query.all() if n_utils.is_port_bound(p)]
14301417
# TODO(slaweq): if there would be way to pass to neutron-lib only
14311418
# list of extensions which actually should be processed, than setting
14321419
# process_extensions=True below could avoid that second loop and

neutron/services/trunk/drivers/ovn/trunk_driver.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
from oslo_log import log
2323

2424
from neutron.common.ovn import constants as ovn_const
25+
from neutron.common import utils as n_utils
2526
from neutron.db import db_base_plugin_common
2627
from neutron.db import ovn_revision_numbers_db as db_rev
2728
from neutron.objects import ports as port_obj
2829
from neutron.services.trunk.drivers import base as trunk_base
30+
from neutron.services.trunk import exceptions as trunk_exc
2931

3032

3133
SUPPORTED_INTERFACES = (
@@ -162,6 +164,10 @@ def _unset_binding_profile(self, context, subport, ovn_txn):
162164
LOG.debug("Done unsetting parent for subport %s", subport.port_id)
163165
return db_port
164166

167+
@staticmethod
168+
def _is_port_bound(port):
169+
return n_utils.is_port_bound(port, log_message=False)
170+
165171
def trunk_updated(self, trunk):
166172
# Check if parent port is handled by OVN.
167173
if not self.plugin_driver.nb_ovn.lookup('Logical_Switch_Port',
@@ -208,6 +214,16 @@ def trunk_event(self, resource, event, trunk_plugin, payload):
208214
self.trunk_updated(payload.states[0])
209215
elif event == events.AFTER_DELETE:
210216
self.trunk_deleted(payload.states[0])
217+
elif event == events.PRECOMMIT_CREATE:
218+
trunk = payload.desired_state
219+
parent_port = trunk.db_obj.port
220+
if self._is_port_bound(parent_port):
221+
raise trunk_exc.ParentPortInUse(port_id=parent_port.id)
222+
elif event == events.PRECOMMIT_DELETE:
223+
trunk = payload.states[0]
224+
parent_port = payload.states[1]
225+
if self._is_port_bound(parent_port):
226+
raise trunk_exc.TrunkInUse(trunk_id=trunk.id)
211227

212228
def subport_event(self, resource, event, trunk_plugin, payload):
213229
if event == events.AFTER_CREATE:
@@ -233,7 +249,8 @@ def register(self, resource, event, trigger, payload=None):
233249
resource, event, trigger, payload=payload)
234250
self._handler = OVNTrunkHandler(self.plugin_driver)
235251
for _event in (events.AFTER_CREATE, events.AFTER_UPDATE,
236-
events.AFTER_DELETE):
252+
events.AFTER_DELETE, events.PRECOMMIT_CREATE,
253+
events.PRECOMMIT_DELETE):
237254
registry.subscribe(self._handler.trunk_event,
238255
resources.TRUNK,
239256
_event)

neutron/services/trunk/plugin.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ def delete_trunk(self, context, trunk_id):
294294
trunk = self._get_trunk(context, trunk_id)
295295
rules.trunk_can_be_managed(context, trunk)
296296
trunk_port_validator = rules.TrunkPortValidator(trunk.port_id)
297+
parent_port = trunk.db_obj.port
297298
if trunk_port_validator.can_be_trunked_or_untrunked(context):
298299
# NOTE(status_police): when a trunk is deleted, the logical
299300
# object disappears from the datastore, therefore there is no
@@ -307,7 +308,7 @@ def delete_trunk(self, context, trunk_id):
307308
'deleting trunk port %s: %s', trunk_id,
308309
str(e))
309310
payload = events.DBEventPayload(context, resource_id=trunk_id,
310-
states=(trunk,))
311+
states=(trunk, parent_port))
311312
registry.publish(resources.TRUNK, events.PRECOMMIT_DELETE,
312313
self, payload=payload)
313314
else:
@@ -317,7 +318,7 @@ def delete_trunk(self, context, trunk_id):
317318
registry.publish(resources.TRUNK, events.AFTER_DELETE, self,
318319
payload=events.DBEventPayload(
319320
context, resource_id=trunk_id,
320-
states=(trunk,)))
321+
states=(trunk, parent_port)))
321322

322323
@db_base_plugin_common.convert_result_to_dict
323324
def add_subports(self, context, trunk_id, subports):

neutron/tests/functional/services/trunk/drivers/ovn/test_trunk_driver.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import contextlib
1616

1717
from neutron_lib.api.definitions import portbindings
18+
from neutron_lib.callbacks import exceptions as n_exc
1819
from neutron_lib import constants as n_consts
1920
from neutron_lib.db import api as db_api
2021
from neutron_lib.plugins import utils
@@ -113,6 +114,25 @@ def test_trunk_create_with_subports(self):
113114
with self.trunk([subport]) as trunk:
114115
self._verify_trunk_info(trunk, has_items=True)
115116

117+
def test_trunk_create_parent_port_bound(self):
118+
with self.network() as network:
119+
with self.subnet(network=network) as subnet:
120+
with self.port(subnet=subnet) as parent_port:
121+
pb = port_obj.PortBinding.get_objects(
122+
self.context, port_id=parent_port['port']['id'])
123+
port_obj.PortBinding.update_object(
124+
self.context, {'vif_type': portbindings.VIF_TYPE_OVS},
125+
port_id=pb[0].port_id, host=pb[0].host)
126+
tenant_id = uuidutils.generate_uuid()
127+
trunk = {'trunk': {
128+
'port_id': parent_port['port']['id'],
129+
'tenant_id': tenant_id, 'project_id': tenant_id,
130+
'admin_state_up': True,
131+
'name': 'trunk', 'sub_ports': []}}
132+
self.assertRaises(n_exc.CallbackFailure,
133+
self.trunk_plugin.create_trunk,
134+
self.context, trunk)
135+
116136
def test_subport_add(self):
117137
with self.subport() as subport:
118138
with self.trunk() as trunk:
@@ -147,3 +167,14 @@ def test_trunk_delete(self):
147167
with self.trunk() as trunk:
148168
self.trunk_plugin.delete_trunk(self.context, trunk['id'])
149169
self._verify_trunk_info({}, has_items=False)
170+
171+
def test_trunk_delete_parent_port_bound(self):
172+
with self.trunk() as trunk:
173+
bp = port_obj.PortBinding.get_objects(
174+
self.context, port_id=trunk['port_id'])
175+
port_obj.PortBinding.update_object(
176+
self.context, {'vif_type': portbindings.VIF_TYPE_OVS},
177+
port_id=bp[0].port_id, host=bp[0].host)
178+
self.assertRaises(n_exc.CallbackFailure,
179+
self.trunk_plugin.delete_trunk,
180+
self.context, trunk['id'])

neutron/tests/unit/db/test_l3_dvr_db.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from neutron_lib.plugins import utils as plugin_utils
3131
from oslo_utils import uuidutils
3232

33+
from neutron.common import utils as n_utils
3334
from neutron.db import agents_db
3435
from neutron.db import l3_dvr_db
3536
from neutron.db import l3_dvrscheduler_db
@@ -1521,7 +1522,7 @@ def test_is_router_distributed(self):
15211522
self.assertTrue(
15221523
self.mixin.is_router_distributed(self.ctx, router_id))
15231524

1524-
@mock.patch.object(l3_dvr_db, "is_port_bound")
1525+
@mock.patch.object(n_utils, 'is_port_bound')
15251526
def test_get_ports_under_dvr_connected_subnet(self, is_port_bound_mock):
15261527
router_dict = {'name': 'test_router', 'admin_state_up': True,
15271528
'distributed': True}

neutron/tests/unit/services/trunk/test_plugin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ def _test_trunk_delete_notify(self, event):
162162
resources.TRUNK, event, self.trunk_plugin, payload=mock.ANY)
163163
payload = callback.mock_calls[0][2]['payload']
164164
self.assertEqual(self.context, payload.context)
165-
self.assertEqual(trunk_obj, payload.latest_state)
165+
self.assertEqual(trunk_obj, payload.states[0])
166+
self.assertEqual(parent_port['port']['id'], payload.states[1].id)
166167
self.assertEqual(trunk['id'], payload.resource_id)
167168

168169
def test_delete_trunk_notify_after_delete(self):
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
fixes:
3+
- |
4+
Now the ML2/OVN trunk driver prevents a trunk creation if the parent port
5+
is already bound. In the same way, if a parent port being used in a trunk
6+
is bound, the trunk cannot be deleted.

0 commit comments

Comments
 (0)