Skip to content

Commit 03656bd

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "[OVN] Update lsp host id when virtual parent moves" into stable/2023.1
2 parents f9585ef + ebd52b0 commit 03656bd

File tree

3 files changed

+110
-3
lines changed

3 files changed

+110
-3
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,9 +1053,19 @@ def update_virtual_port_host(self, port_id, chassis_id):
10531053
'Chassis', chassis_id, 'hostname').execute(check_error=True)
10541054
else:
10551055
hostname = ''
1056+
1057+
# Updates neutron database with hostname for virtual port
10561058
self._plugin.update_virtual_port_host(n_context.get_admin_context(),
10571059
port_id, hostname)
10581060

1061+
# Updates OVN NB database with hostname for lsp virtual port
1062+
with self.nb_ovn.transaction(check_error=True) as txn:
1063+
ext_ids = ('external_ids',
1064+
{ovn_const.OVN_HOST_ID_EXT_ID_KEY: hostname})
1065+
txn.add(
1066+
self.nb_ovn.db_set(
1067+
'Logical_Switch_Port', port_id, ext_ids))
1068+
10591069
def get_workers(self):
10601070
"""Get any worker instances that should have their own process
10611071

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,17 @@ def match_fn(self, event, row, old):
566566

567567
virtual_parents = (row.options or {}).get(
568568
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
569+
570+
if getattr(old, 'chassis', None) is not None and virtual_parents:
571+
# The port moved from chassis due to VIP failover or migration,
572+
# which means we need to update the host_id information
573+
return True
574+
575+
if getattr(old, 'options', None) is not None:
576+
# The "old.options" dictionary is not being modified,
577+
# thus the virtual parents didn't change.
578+
return False
579+
569580
old_virtual_parents = getattr(old, 'options', {}).get(
570581
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
571582
if virtual_parents != old_virtual_parents:

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

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from ovsdbapp.backend.ovs_idl import event
2929
from ovsdbapp.backend.ovs_idl import idlutils
3030
import tenacity
31+
import testtools
3132

3233
from neutron.common.ovn import constants as ovn_const
3334
from neutron.common import utils as n_utils
@@ -323,9 +324,11 @@ def _set_port_binding_virtual_parent(self, port_id, parent_port_id):
323324
pb_port_vip = self.sb_api.db_find_rows(
324325
'Port_Binding', ('logical_port', '=', port_id)).execute(
325326
check_error=True)[0]
327+
pb_virtual_parent = str(pb_port_parent.uuid)
326328
self.sb_api.db_set(
327329
'Port_Binding', pb_port_vip.uuid,
328-
('virtual_parent', pb_port_parent.uuid)).execute(check_error=True)
330+
('chassis', pb_port_parent.chassis),
331+
('virtual_parent', pb_virtual_parent)).execute(check_error=True)
329332

330333
def _check_port_binding_type(self, port_id, port_type):
331334
def is_port_binding_type(port_id, port_type):
@@ -338,14 +341,26 @@ def is_port_binding_type(port_id, port_type):
338341
def _check_port_virtual_parents(self, port_id, vparents):
339342
def is_port_virtual_parents(port_id, vparents):
340343
bp = self._find_port_binding(port_id)
341-
return (vparents ==
342-
bp.options.get(ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY))
344+
vp = bp.options.get(ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY)
345+
346+
# If the given vparents is None, or if the current value is None
347+
# Then just do a check on that, no need to split strings if it
348+
# is not required
349+
if None in (vp, vparents):
350+
return vp == vparents
351+
352+
# Since the virtual parents is a string, representing a list of
353+
# ports, we should make a set() and compare sets
354+
bp_set = {p for p in vp.split(',')}
355+
vp_set = {p for p in vparents.split(',')}
356+
return bp_set == vp_set
343357

344358
check = functools.partial(is_port_virtual_parents, port_id, vparents)
345359
n_utils.wait_until_true(check, timeout=10)
346360

347361
@mock.patch.object(mech_driver.OVNMechanismDriver,
348362
'update_virtual_port_host')
363+
@testtools.skip('will be recovery by following patch in chain 2038413')
349364
def test_virtual_port_host_update(self, mock_update_vip_host):
350365
# NOTE: because we can't simulate traffic from a port, this check is
351366
# not done in this test. This test checks the VIP host is unset when
@@ -417,6 +432,77 @@ def test_non_virtual_port_no_host_update(self, mock_update_vip_host):
417432
self.assertRaises(n_utils.WaitTimeout, n_utils.wait_until_true,
418433
lambda: mock_update_vip_host.called, timeout=5)
419434

435+
def _check_port_host_set(self, port_id, host_id):
436+
# This function checks if given host_id matches the values in the
437+
# neutron DB as well as in the OVN DB for the port with given port_id
438+
core_plugin = directory.get_plugin()
439+
440+
# Get port from neutron DB
441+
port = core_plugin.get_ports(
442+
self.context, filters={'id': [port_id]})[0]
443+
444+
# Get port from OVN DB
445+
bp = self._find_port_binding(port_id)
446+
ovn_host_id = bp.external_ids.get(ovn_const.OVN_HOST_ID_EXT_ID_KEY)
447+
448+
# Check that both neutron and ovn are the same as given host_id
449+
return port[portbindings.HOST_ID] == host_id == ovn_host_id
450+
451+
def test_virtual_port_host_update_upon_failover(self):
452+
# NOTE: we can't simulate traffic, but we can simulate the event that
453+
# would've been triggered by OVN, which is what we do.
454+
455+
# The test is based to test_virtual_port_host_update, though in this
456+
# test we actually test the OVNMechanismDriver.update_virtual_port_host
457+
# method, that updates the hostname in neutron and OVN
458+
# We do not extensively check if the allowed-address-pair is being kept
459+
# up-to-date, since test_virtual_port_host_update does this already.
460+
461+
# 1) Setup a second chassis
462+
second_chassis_name = 'ovs-host2'
463+
second_chassis = self.add_fake_chassis(second_chassis_name)
464+
465+
# 2) Create port with the VIP for allowed address pair setup
466+
vip = self.create_port(device_owner='', host='')
467+
vip_address = vip['fixed_ips'][0]['ip_address']
468+
allowed_address_pairs = [{'ip_address': vip_address}]
469+
self._check_port_binding_type(vip['id'], '')
470+
471+
# 3) Create two ports with the allowed address pairs set.
472+
hosts = ('ovs-host1', second_chassis_name)
473+
ports = []
474+
for idx in range(len(hosts)):
475+
ports.append(self.create_port(host=hosts[idx]))
476+
data = {'port': {'allowed_address_pairs': allowed_address_pairs}}
477+
req = self.new_update_request('ports', data, ports[idx]['id'])
478+
req.get_response(self.api)
479+
480+
port_ids = [p['id'] for p in ports]
481+
482+
# 4) Check that the vip port has become virtual and that both parents
483+
# have been assigned to the port binding
484+
self._check_port_binding_type(vip['id'], ovn_const.LSP_TYPE_VIRTUAL)
485+
self._check_port_virtual_parents(vip['id'], ','.join(port_ids))
486+
487+
# 5) Bind the ports to a host, so a chassis is bound, which is
488+
# required for the update_virtual_port_host method. Without this
489+
# chassis set, it will not set a hostname in the DB's
490+
self._test_port_binding_and_status(ports[0]['id'], 'bind', 'ACTIVE')
491+
self.chassis = second_chassis
492+
self._test_port_binding_and_status(ports[1]['id'], 'bind', 'ACTIVE')
493+
494+
# 6) For both ports, bind vip on parent and check hostname in DBs
495+
for idx in range(len(ports)):
496+
# Set port binding to the first port, and update the chassis
497+
self._set_port_binding_virtual_parent(vip['id'], ports[idx]['id'])
498+
499+
# Check if the host_id has been updated in OVN and DB
500+
# by the event that eventually calls for method
501+
# OVNMechanismDriver.update_virtual_port_host
502+
n_utils.wait_until_true(
503+
lambda: self._check_port_host_set(vip['id'], hosts[idx]),
504+
timeout=10)
505+
420506

421507
class TestNBDbMonitorOverTcp(TestNBDbMonitor):
422508
def get_ovsdb_server_protocol(self):

0 commit comments

Comments
 (0)