Merge "Improve the ``PortBindingUpdateVirtualPortsEvent`` match filter"

changes/07/883907/2
Zuul 2023-06-06 18:34:22 +00:00 committed by Gerrit Code Review
commit 7431d2f86e
3 changed files with 74 additions and 26 deletions

View File

@ -538,43 +538,42 @@ class PortBindingUpdateVirtualPortsEvent(row_event.RowEvent):
def __init__(self, driver):
self.driver = driver
table = 'Port_Binding'
events = (self.ROW_UPDATE, )
events = (self.ROW_UPDATE, self.ROW_DELETE)
super().__init__(events, table, None)
self.event_name = 'PortBindingUpdateVirtualPortsEvent'
def match_fn(self, event, row, old):
# This event should catch only those events from ports that are
# "virtual" or have been "virtual". The second happens when all virtual
# parent are disassociated; in the same transaction the
# "virtual-parents" list is removed from "options" and the type is set
# to "".
if (row.type != ovn_const.PB_TYPE_VIRTUAL and
getattr(old, 'type', None) != ovn_const.PB_TYPE_VIRTUAL):
return False
# This event should catch the events related to virtual parents (that
# are associated to virtual ports).
if event == self.ROW_DELETE:
# The port binding has been deleted, delete the host ID (if the
# port was not deleted before).
return True
virtual_parents = (row.options or {}).get(
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
old_virtual_parents = getattr(old, 'options', {}).get(
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
chassis = row.chassis
old_chassis = getattr(old, 'chassis', [])
if virtual_parents and chassis != old_chassis:
# That happens when the chassis is assigned (VIP is first detected
# in a port) or changed (the VIP changes of assigned port and
# host).
return True
if not virtual_parents and old_virtual_parents:
if virtual_parents != old_virtual_parents:
# 1) if virtual_parents and not old_virtual_parents:
# The port has received a virtual parent and now is bound.
# 2) elif (virtual_parents and old_virtual_parents and
# old_virtual_parents != virtual_parents):
# If the port virtual parents have changed (the VIP is bound
# to another host because it's owned by another port).
# 3) if not virtual_parents and old_virtual_parents:
# All virtual parent ports are removed, the VIP is unbound.
return True
return False
def run(self, event, row, old):
virtual_parents = (row.options or {}).get(
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
chassis_uuid = (row.chassis[0].uuid if
row.chassis and virtual_parents else None)
if event == self.ROW_DELETE:
chassis_uuid = None
else:
virtual_parents = (row.options or {}).get(
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
chassis_uuid = (row.chassis[0].uuid if
row.chassis and virtual_parents else None)
self.driver.update_virtual_port_host(row.logical_port, chassis_uuid)

View File

@ -2068,8 +2068,14 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
"""
hostname = hostname or ''
with db_api.CONTEXT_WRITER.using(context):
for pb in ports_obj.PortBinding.get_objects(context,
port_id=port_id):
pbindings = ports_obj.PortBinding.get_objects(context,
port_id=port_id)
if not pbindings:
# The port has been deleted and there is no need to delete and
# create any port binding.
return
for pb in pbindings:
pb.delete()
attrs = {'port_id': port_id,

View File

@ -312,6 +312,17 @@ class TestNBDbMonitor(base.TestOVNFunctionalBase):
rows = cmd.execute(check_error=True)
return rows[0] if rows else None
def _set_port_binding_virtual_parent(self, port_id, parent_port_id):
pb_port_parent = self.sb_api.db_find_rows(
'Port_Binding', ('logical_port', '=', parent_port_id)).execute(
check_error=True)[0]
pb_port_vip = self.sb_api.db_find_rows(
'Port_Binding', ('logical_port', '=', port_id)).execute(
check_error=True)[0]
self.sb_api.db_set(
'Port_Binding', pb_port_vip.uuid,
('virtual_parent', pb_port_parent.uuid)).execute(check_error=True)
def _check_port_binding_type(self, port_id, port_type):
def is_port_binding_type(port_id, port_type):
bp = self._find_port_binding(port_id)
@ -341,6 +352,7 @@ class TestNBDbMonitor(base.TestOVNFunctionalBase):
port = self.create_port()
self._check_port_binding_type(vip['id'], '')
# 1) Set the allowed address pairs.
data = {'port': {'allowed_address_pairs': allowed_address_pairs}}
req = self.new_update_request('ports', data, port['id'])
req.get_response(self.api)
@ -348,8 +360,21 @@ class TestNBDbMonitor(base.TestOVNFunctionalBase):
# and the corresponding "virtual-parents".
self._check_port_binding_type(vip['id'], ovn_const.LSP_TYPE_VIRTUAL)
self._check_port_virtual_parents(vip['id'], port['id'])
mock_update_vip_host.assert_not_called()
n_utils.wait_until_true(lambda: mock_update_vip_host.called,
timeout=10)
# The "Port_Binding" has been deleted. Then the "Port_Binding" register
# is created again without virtual_parents, but this event doesn't
# call "update_virtual_port_host".
mock_update_vip_host.assert_called_once_with(vip['id'], None)
# 2) Unset the allowed address pairs.
# Assign the VIP again and delete the virtual port.
# Before unsetting the allowed address pairs, we first manually add
# the Port_Binding.virtual_parent of the virtual port. That happens
# when an ovn-controller detects traffic with the VIP and assign the
# port hosting the VIP as virtual parent.
self._set_port_binding_virtual_parent(vip['id'], port['id'])
mock_update_vip_host.reset_mock()
data = {'port': {'allowed_address_pairs': []}}
req = self.new_update_request('ports', data, port['id'])
req.get_response(self.api)
@ -357,6 +382,24 @@ class TestNBDbMonitor(base.TestOVNFunctionalBase):
self._check_port_virtual_parents(vip['id'], None)
n_utils.wait_until_true(lambda: mock_update_vip_host.called,
timeout=10)
# The virtual port is no longer considered as virtual. The
# "Port_Binding" register is deleted.
mock_update_vip_host.assert_called_once_with(vip['id'], None)
# 3) Set again the allowed address pairs.
mock_update_vip_host.reset_mock()
data = {'port': {'allowed_address_pairs': allowed_address_pairs}}
req = self.new_update_request('ports', data, port['id'])
req.get_response(self.api)
# This test checks that the VIP "Port_Binding" register gets the type
# and the corresponding "virtual-parents".
self._check_port_binding_type(vip['id'], ovn_const.LSP_TYPE_VIRTUAL)
self._check_port_virtual_parents(vip['id'], port['id'])
mock_update_vip_host.reset_mock()
self._delete('ports', vip['id'])
n_utils.wait_until_true(lambda: mock_update_vip_host.called,
timeout=10)
# The virtual port is deleted and so the associated "Port_Binding".
mock_update_vip_host.assert_called_once_with(vip['id'], None)