Skip to content

Commit fbda749

Browse files
ralonsohmnaser
authored andcommitted
Improve the PortBindingUpdateVirtualPortsEvent match filter
This patch improves the ``PortBindingUpdateVirtualPortsEvent`` match filter. These are the new conditions: * Event delete: that happens when the port binding has been deleted because the port is no longer bound or the port has been deleted. That will remove the Neutron port host name. NOTE: in case the Neutron port has been deleted, the method ``Ml2Plugin.update_virtual_port_host`` won't update (create) a new PortBinding object. * If the new register has virtual_parents but not the old one, that means the ovn-controller has received traffic with the VIP from this port. The port host ID must be set. * If the virtual parents have changed, the port host ID must be updated. * If the virtual parents have been removed, the port host ID must be removed too. Newer versions of OVN [1] are handling the virtual port binding in a different way. When the virtual parents are added or removed, the related "Port_Binding" register is deleted and the created again. This is why this new version includes the event "DELETE" on the match method; when the register is deleted, the event class considers that the port is no longer bound to a host and removes the host name for the Neutron port. [1]https://review.opendev.org/c/openstack/neutron/+/880890/ Change-Id: I34caf7d0212ccb4bd7259c4414e7c3994bd8da4d (cherry picked from commit 2fbfe38)
1 parent 4428c8a commit fbda749

File tree

3 files changed

+74
-26
lines changed

3 files changed

+74
-26
lines changed

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -547,43 +547,42 @@ class PortBindingUpdateVirtualPortsEvent(row_event.RowEvent):
547547
def __init__(self, driver):
548548
self.driver = driver
549549
table = 'Port_Binding'
550-
events = (self.ROW_UPDATE, )
550+
events = (self.ROW_UPDATE, self.ROW_DELETE)
551551
super().__init__(events, table, None)
552552
self.event_name = 'PortBindingUpdateVirtualPortsEvent'
553553

554554
def match_fn(self, event, row, old):
555-
# This event should catch only those events from ports that are
556-
# "virtual" or have been "virtual". The second happens when all virtual
557-
# parent are disassociated; in the same transaction the
558-
# "virtual-parents" list is removed from "options" and the type is set
559-
# to "".
560-
if (row.type != ovn_const.PB_TYPE_VIRTUAL and
561-
getattr(old, 'type', None) != ovn_const.PB_TYPE_VIRTUAL):
562-
return False
555+
# This event should catch the events related to virtual parents (that
556+
# are associated to virtual ports).
557+
if event == self.ROW_DELETE:
558+
# The port binding has been deleted, delete the host ID (if the
559+
# port was not deleted before).
560+
return True
563561

564562
virtual_parents = (row.options or {}).get(
565563
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
566564
old_virtual_parents = getattr(old, 'options', {}).get(
567565
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
568-
chassis = row.chassis
569-
old_chassis = getattr(old, 'chassis', [])
570-
571-
if virtual_parents and chassis != old_chassis:
572-
# That happens when the chassis is assigned (VIP is first detected
573-
# in a port) or changed (the VIP changes of assigned port and
574-
# host).
575-
return True
576-
577-
if not virtual_parents and old_virtual_parents:
566+
if virtual_parents != old_virtual_parents:
567+
# 1) if virtual_parents and not old_virtual_parents:
568+
# The port has received a virtual parent and now is bound.
569+
# 2) elif (virtual_parents and old_virtual_parents and
570+
# old_virtual_parents != virtual_parents):
571+
# If the port virtual parents have changed (the VIP is bound
572+
# to another host because it's owned by another port).
573+
# 3) if not virtual_parents and old_virtual_parents:
578574
# All virtual parent ports are removed, the VIP is unbound.
579575
return True
580576
return False
581577

582578
def run(self, event, row, old):
583-
virtual_parents = (row.options or {}).get(
584-
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
585-
chassis_uuid = (row.chassis[0].uuid if
586-
row.chassis and virtual_parents else None)
579+
if event == self.ROW_DELETE:
580+
chassis_uuid = None
581+
else:
582+
virtual_parents = (row.options or {}).get(
583+
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
584+
chassis_uuid = (row.chassis[0].uuid if
585+
row.chassis and virtual_parents else None)
587586
self.driver.update_virtual_port_host(row.logical_port, chassis_uuid)
588587

589588

neutron/plugins/ml2/plugin.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,8 +2068,14 @@ def update_virtual_port_host(self, context, port_id, hostname):
20682068
"""
20692069
hostname = hostname or ''
20702070
with db_api.CONTEXT_WRITER.using(context):
2071-
for pb in ports_obj.PortBinding.get_objects(context,
2072-
port_id=port_id):
2071+
pbindings = ports_obj.PortBinding.get_objects(context,
2072+
port_id=port_id)
2073+
if not pbindings:
2074+
# The port has been deleted and there is no need to delete and
2075+
# create any port binding.
2076+
return
2077+
2078+
for pb in pbindings:
20732079
pb.delete()
20742080

20752081
attrs = {'port_id': port_id,

neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovsdb_monitor.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,17 @@ def _find_port_binding(self, port_id):
311311
rows = cmd.execute(check_error=True)
312312
return rows[0] if rows else None
313313

314+
def _set_port_binding_virtual_parent(self, port_id, parent_port_id):
315+
pb_port_parent = self.sb_api.db_find_rows(
316+
'Port_Binding', ('logical_port', '=', parent_port_id)).execute(
317+
check_error=True)[0]
318+
pb_port_vip = self.sb_api.db_find_rows(
319+
'Port_Binding', ('logical_port', '=', port_id)).execute(
320+
check_error=True)[0]
321+
self.sb_api.db_set(
322+
'Port_Binding', pb_port_vip.uuid,
323+
('virtual_parent', pb_port_parent.uuid)).execute(check_error=True)
324+
314325
def _check_port_binding_type(self, port_id, port_type):
315326
def is_port_binding_type(port_id, port_type):
316327
bp = self._find_port_binding(port_id)
@@ -340,22 +351,54 @@ def test_virtual_port_host_update(self, mock_update_vip_host):
340351
port = self.create_port()
341352
self._check_port_binding_type(vip['id'], '')
342353

354+
# 1) Set the allowed address pairs.
343355
data = {'port': {'allowed_address_pairs': allowed_address_pairs}}
344356
req = self.new_update_request('ports', data, port['id'])
345357
req.get_response(self.api)
346358
# This test checks that the VIP "Port_Binding" register gets the type
347359
# and the corresponding "virtual-parents".
348360
self._check_port_binding_type(vip['id'], ovn_const.LSP_TYPE_VIRTUAL)
349361
self._check_port_virtual_parents(vip['id'], port['id'])
350-
mock_update_vip_host.assert_not_called()
362+
n_utils.wait_until_true(lambda: mock_update_vip_host.called,
363+
timeout=10)
364+
# The "Port_Binding" has been deleted. Then the "Port_Binding" register
365+
# is created again without virtual_parents, but this event doesn't
366+
# call "update_virtual_port_host".
367+
mock_update_vip_host.assert_called_once_with(vip['id'], None)
351368

369+
# 2) Unset the allowed address pairs.
370+
# Assign the VIP again and delete the virtual port.
371+
# Before unsetting the allowed address pairs, we first manually add
372+
# the Port_Binding.virtual_parent of the virtual port. That happens
373+
# when an ovn-controller detects traffic with the VIP and assign the
374+
# port hosting the VIP as virtual parent.
375+
self._set_port_binding_virtual_parent(vip['id'], port['id'])
376+
mock_update_vip_host.reset_mock()
352377
data = {'port': {'allowed_address_pairs': []}}
353378
req = self.new_update_request('ports', data, port['id'])
354379
req.get_response(self.api)
355380
self._check_port_binding_type(vip['id'], '')
356381
self._check_port_virtual_parents(vip['id'], None)
357382
n_utils.wait_until_true(lambda: mock_update_vip_host.called,
358383
timeout=10)
384+
# The virtual port is no longer considered as virtual. The
385+
# "Port_Binding" register is deleted.
386+
mock_update_vip_host.assert_called_once_with(vip['id'], None)
387+
388+
# 3) Set again the allowed address pairs.
389+
mock_update_vip_host.reset_mock()
390+
data = {'port': {'allowed_address_pairs': allowed_address_pairs}}
391+
req = self.new_update_request('ports', data, port['id'])
392+
req.get_response(self.api)
393+
# This test checks that the VIP "Port_Binding" register gets the type
394+
# and the corresponding "virtual-parents".
395+
self._check_port_binding_type(vip['id'], ovn_const.LSP_TYPE_VIRTUAL)
396+
self._check_port_virtual_parents(vip['id'], port['id'])
397+
mock_update_vip_host.reset_mock()
398+
self._delete('ports', vip['id'])
399+
n_utils.wait_until_true(lambda: mock_update_vip_host.called,
400+
timeout=10)
401+
# The virtual port is deleted and so the associated "Port_Binding".
359402
mock_update_vip_host.assert_called_once_with(vip['id'], None)
360403

361404

0 commit comments

Comments
 (0)