Merge "[OVN] Update lsp host id when virtual parent moves"

This commit is contained in:
Zuul 2024-01-17 18:08:17 +00:00 committed by Gerrit Code Review
commit e6a7cc26a0
3 changed files with 108 additions and 3 deletions
neutron
plugins/ml2/drivers/ovn/mech_driver
tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb

@ -1064,9 +1064,19 @@ class OVNMechanismDriver(api.MechanismDriver):
'Chassis', chassis_id, 'hostname').execute(check_error=True)
else:
hostname = ''
# Updates neutron database with hostname for virtual port
self._plugin.update_virtual_port_host(n_context.get_admin_context(),
port_id, hostname)
# Updates OVN NB database with hostname for lsp virtual port
with self.nb_ovn.transaction(check_error=True) as txn:
ext_ids = ('external_ids',
{ovn_const.OVN_HOST_ID_EXT_ID_KEY: hostname})
txn.add(
self.nb_ovn.db_set(
'Logical_Switch_Port', port_id, ext_ids))
def get_workers(self):
"""Get any worker instances that should have their own process

@ -562,6 +562,17 @@ class PortBindingUpdateVirtualPortsEvent(row_event.RowEvent):
virtual_parents = (row.options or {}).get(
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
if getattr(old, 'chassis', None) is not None and virtual_parents:
# The port moved from chassis due to VIP failover or migration,
# which means we need to update the host_id information
return True
if getattr(old, 'options', None) is not None:
# The "old.options" dictionary is not being modified,
# thus the virtual parents didn't change.
return False
old_virtual_parents = getattr(old, 'options', {}).get(
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
if virtual_parents != old_virtual_parents:

@ -319,9 +319,11 @@ class TestNBDbMonitor(base.TestOVNFunctionalBase):
pb_port_vip = self.sb_api.db_find_rows(
'Port_Binding', ('logical_port', '=', port_id)).execute(
check_error=True)[0]
pb_virtual_parent = str(pb_port_parent.uuid)
self.sb_api.db_set(
'Port_Binding', pb_port_vip.uuid,
('virtual_parent', pb_port_parent.uuid)).execute(check_error=True)
('chassis', pb_port_parent.chassis),
('virtual_parent', pb_virtual_parent)).execute(check_error=True)
def _check_port_binding_type(self, port_id, port_type):
def is_port_binding_type(port_id, port_type):
@ -334,8 +336,19 @@ class TestNBDbMonitor(base.TestOVNFunctionalBase):
def _check_port_virtual_parents(self, port_id, vparents):
def is_port_virtual_parents(port_id, vparents):
bp = self._find_port_binding(port_id)
return (vparents ==
bp.options.get(ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY))
vp = bp.options.get(ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
# If the given vparents is None, or if the current value is None
# Then just do a check on that, no need to split strings if it
# is not required
if None in (vp, vparents):
return vp == vparents
# Since the virtual parents is a string, representing a list of
# ports, we should make a set() and compare sets
bp_set = {p for p in vp.split(',')}
vp_set = {p for p in vparents.split(',')}
return bp_set == vp_set
check = functools.partial(is_port_virtual_parents, port_id, vparents)
n_utils.wait_until_true(check, timeout=10)
@ -413,6 +426,77 @@ class TestNBDbMonitor(base.TestOVNFunctionalBase):
self.assertRaises(n_utils.WaitTimeout, n_utils.wait_until_true,
lambda: mock_update_vip_host.called, timeout=5)
def _check_port_host_set(self, port_id, host_id):
# This function checks if given host_id matches the values in the
# neutron DB as well as in the OVN DB for the port with given port_id
core_plugin = directory.get_plugin()
# Get port from neutron DB
port = core_plugin.get_ports(
self.context, filters={'id': [port_id]})[0]
# Get port from OVN DB
bp = self._find_port_binding(port_id)
ovn_host_id = bp.external_ids.get(ovn_const.OVN_HOST_ID_EXT_ID_KEY)
# Check that both neutron and ovn are the same as given host_id
return port[portbindings.HOST_ID] == host_id == ovn_host_id
def test_virtual_port_host_update_upon_failover(self):
# NOTE: we can't simulate traffic, but we can simulate the event that
# would've been triggered by OVN, which is what we do.
# The test is based to test_virtual_port_host_update, though in this
# test we actually test the OVNMechanismDriver.update_virtual_port_host
# method, that updates the hostname in neutron and OVN
# We do not extensively check if the allowed-address-pair is being kept
# up-to-date, since test_virtual_port_host_update does this already.
# 1) Setup a second chassis
second_chassis_name = 'ovs-host2'
second_chassis = self.add_fake_chassis(second_chassis_name)
# 2) Create port with the VIP for allowed address pair setup
vip = self.create_port(device_owner='', host='')
vip_address = vip['fixed_ips'][0]['ip_address']
allowed_address_pairs = [{'ip_address': vip_address}]
self._check_port_binding_type(vip['id'], '')
# 3) Create two ports with the allowed address pairs set.
hosts = ('ovs-host1', second_chassis_name)
ports = []
for idx in range(len(hosts)):
ports.append(self.create_port(host=hosts[idx]))
data = {'port': {'allowed_address_pairs': allowed_address_pairs}}
req = self.new_update_request('ports', data, ports[idx]['id'])
req.get_response(self.api)
port_ids = [p['id'] for p in ports]
# 4) Check that the vip port has become virtual and that both parents
# have been assigned to the port binding
self._check_port_binding_type(vip['id'], ovn_const.LSP_TYPE_VIRTUAL)
self._check_port_virtual_parents(vip['id'], ','.join(port_ids))
# 5) Bind the ports to a host, so a chassis is bound, which is
# required for the update_virtual_port_host method. Without this
# chassis set, it will not set a hostname in the DB's
self._test_port_binding_and_status(ports[0]['id'], 'bind', 'ACTIVE')
self.chassis = second_chassis
self._test_port_binding_and_status(ports[1]['id'], 'bind', 'ACTIVE')
# 6) For both ports, bind vip on parent and check hostname in DBs
for idx in range(len(ports)):
# Set port binding to the first port, and update the chassis
self._set_port_binding_virtual_parent(vip['id'], ports[idx]['id'])
# Check if the host_id has been updated in OVN and DB
# by the event that eventually calls for method
# OVNMechanismDriver.update_virtual_port_host
n_utils.wait_until_true(
lambda: self._check_port_host_set(vip['id'], hosts[idx]),
timeout=10)
class TestNBDbMonitorOverTcp(TestNBDbMonitor):
def get_ovsdb_server_protocol(self):