Merge "ovn_db_sync: Improve coexistence support."

This commit is contained in:
Zuul
2025-09-25 15:01:06 +00:00
committed by Gerrit Code Review
7 changed files with 196 additions and 40 deletions

View File

@@ -339,11 +339,17 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
if ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY not in (
lrouter.external_ids):
continue
lrports = {lrport.name.replace('lrp-', ''): lrport.networks
for lrport in getattr(lrouter, 'ports', [])}
sroutes = [{'destination': sroute.ip_prefix,
'nexthop': sroute.nexthop}
for sroute in getattr(lrouter, 'static_routes', [])]
lrports = {
lrport.name.replace('lrp-', ''): lrport.networks
for lrport in getattr(lrouter, 'ports', [])
if ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY in lrport.external_ids
}
sroutes = [
{'destination': route.ip_prefix, 'nexthop': route.nexthop}
for route in getattr(lrouter, 'static_routes', [])
if any(eid.startswith(constants.DEVICE_OWNER_NEUTRON_PREFIX)
for eid in route.external_ids)
]
dnat_and_snats = []
snat = []

View File

@@ -573,6 +573,8 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
cmds = []
for ls in self._nb_idl.ls_list().execute(check_error=True):
if ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY not in ls.external_ids:
continue
snooping = ls.other_config.get(ovn_const.MCAST_SNOOP)
flood = ls.other_config.get(ovn_const.MCAST_FLOOD_UNREGISTERED)
@@ -612,9 +614,11 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
context = n_context.get_admin_context()
with self._nb_idl.transaction(check_error=True) as txn:
for port in external_ports:
network_id = port.external_ids[
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY].replace(
network_id = port.external_ids.get(
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY, '').replace(
ovn_const.OVN_NAME_PREFIX, '')
if not network_id:
continue
ha_ch_grp, high_prio_ch = utils.sync_ha_chassis_group_network(
context, self._nb_idl, self._sb_idl, port.name,
network_id, txn)
@@ -1045,6 +1049,9 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
for seg in net_segments}
cmds = []
for ls in self._nb_idl.ls_list().execute(check_error=True):
if ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY not in ls.external_ids:
continue
if ovn_const.OVN_NETTYPE_EXT_ID_KEY not in ls.external_ids:
net_id = utils.get_neutron_name(ls.name)
external_ids = {

View File

@@ -205,7 +205,11 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
ovn_pgs = set()
port_groups = self.ovn_api.db_list_rows('Port_Group').execute() or []
for pg in port_groups:
ovn_pgs.add(pg.name)
# Default neutron "drop pg" does NOT have any external IDs, but
# we still want to manage it, so we match it on its name.
if (ovn_const.OVN_SG_EXT_ID_KEY in pg.external_ids or
pg.name == ovn_const.OVN_DROP_PORT_GROUP_NAME):
ovn_pgs.add(pg.name)
add_pgs = neutron_pgs.difference(ovn_pgs)
remove_pgs = ovn_pgs.difference(neutron_pgs)

View File

@@ -109,6 +109,12 @@ class TestOvnNbSync(testlib_api.MySQLTestCaseMixin,
self.expected_dns_records = []
self.expected_ports_with_unknown_addr = []
self.expected_qos_records = []
# Set of externally managed resources that should not
# be cleaned up by the sync_db
self.create_ext_port_groups = []
self.create_ext_lrouter_ports = []
self.create_ext_lrouter_routes = []
self.ctx = context.get_admin_context()
ovn_config.cfg.CONF.set_override('ovn_metadata_enabled', True,
group='ovn')
@@ -526,6 +532,9 @@ class TestOvnNbSync(testlib_api.MySQLTestCaseMixin,
self.create_lrouter_routes.append(('neutron-' + r1['id'],
'10.13.0.0/24',
'20.0.0.13'))
self.create_ext_lrouter_routes.append(('neutron-' + r1['id'],
'10.14.0.0/24',
'20.0.0.14'))
self.delete_lrouter_routes.append(('neutron-' + r1['id'],
'10.10.0.0/24',
'20.0.0.10'))
@@ -673,10 +682,18 @@ class TestOvnNbSync(testlib_api.MySQLTestCaseMixin,
'neutron-' + r1['id']))
self.create_lrouter_ports.append(('lrp-' + uuidutils.generate_uuid(),
'neutron-' + r1['id']))
self.create_ext_lrouter_ports.append(
('ext-lrp-' + uuidutils.generate_uuid(), 'neutron-' + r1['id'])
)
self.create_ext_lrouter_ports.append(
('ext-lrp-' + uuidutils.generate_uuid(), 'neutron-' + r1['id'])
)
self.delete_lrouters.append('neutron-' + r2['id'])
self.create_port_groups.extend([{'name': 'pg1', 'acls': []},
{'name': 'pg2', 'acls': []}])
self.create_ext_port_groups.extend([{'name': 'ext-pg1', 'acls': []},
{'name': 'ext-pg2', 'acls': []}])
self.delete_port_groups.append(
utils.ovn_port_group_name(n1_prtr['port']['security_groups'][0]))
# Create a network and subnet with orphaned OVN resources.
@@ -801,7 +818,14 @@ class TestOvnNbSync(testlib_api.MySQLTestCaseMixin,
txn.add(self.nb_api.lr_del(lrouter_name, if_exists=True))
for lrport, lrouter_name in self.create_lrouter_ports:
txn.add(self.nb_api.add_lrouter_port(lrport, lrouter_name))
external_ids = {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
lrouter_name}
txn.add(self.nb_api.add_lrouter_port(
lrport, lrouter_name, True, external_ids=external_ids))
for lrport, lrouter_name in self.create_ext_lrouter_ports:
txn.add(self.nb_api.add_lrouter_port(
lrport, lrouter_name, True))
for lrport, lrouter_name, networks in self.update_lrouter_ports:
txn.add(self.nb_api.update_lrouter_port(
@@ -818,6 +842,10 @@ class TestOvnNbSync(testlib_api.MySQLTestCaseMixin,
ip_prefix=ip_prefix,
nexthop=nexthop,
**columns))
for lr_name, ip_prefix, nexthop in self.create_ext_lrouter_routes:
txn.add(self.nb_api.add_static_route(lr_name,
ip_prefix=ip_prefix,
nexthop=nexthop))
routers = defaultdict(list)
for lrouter_name, ip_prefix, nexthop in self.delete_lrouter_routes:
routers[lrouter_name].append((ip_prefix, nexthop))
@@ -850,7 +878,12 @@ class TestOvnNbSync(testlib_api.MySQLTestCaseMixin,
txn.add(self.nb_api.delete_acl(lswitch_name,
lport_name, True))
columns = {
'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'sg_uuid'},
}
for pg in self.create_port_groups:
txn.add(self.nb_api.pg_add(**pg, **columns))
for pg in self.create_ext_port_groups:
txn.add(self.nb_api.pg_add(**pg))
for pg in self.delete_port_groups:
txn.add(self.nb_api.pg_del(pg))
@@ -1308,6 +1341,7 @@ class TestOvnNbSync(testlib_api.MySQLTestCaseMixin,
self.ctx, port))
return ipv6_ra_configs
neutron_prefix = constants.DEVICE_OWNER_NEUTRON_PREFIX
for router_id in db_router_ids:
r_ports = self._list('ports',
query_params='device_id=%s' % (router_id))
@@ -1326,18 +1360,27 @@ class TestOvnNbSync(testlib_api.MySQLTestCaseMixin,
lrouter = idlutils.row_by_value(
self.mech_driver.nb_ovn.idl, 'Logical_Router', 'name',
'neutron-' + str(router_id), None)
lports = getattr(lrouter, 'ports', [])
all_lports = getattr(lrouter, 'ports', [])
managed_lports = [
lport for lport in all_lports
if (ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY in
lport.external_ids)
]
plugin_lrouter_port_ids = [lport.name.replace('lrp-', '')
for lport in lports]
for lport in managed_lports]
plugin_lport_networks = {
lport.name.replace('lrp-', ''): lport.networks
for lport in lports}
for lport in managed_lports}
plugin_lport_ra_configs = {
lport.name.replace('lrp-', ''): lport.ipv6_ra_configs
for lport in lports}
for lport in managed_lports}
sroutes = getattr(lrouter, 'static_routes', [])
plugin_routes = [sroute.ip_prefix + sroute.nexthop
for sroute in sroutes]
plugin_routes = []
for sroute in sroutes:
if any(e_id.startswith(neutron_prefix)
for e_id in sroute.external_ids):
plugin_routes.append(sroute.ip_prefix + sroute.nexthop)
nats = getattr(lrouter, 'nat', [])
plugin_nats = [
nat.external_ip + nat.logical_ip + nat.type +
@@ -1353,18 +1396,29 @@ class TestOvnNbSync(testlib_api.MySQLTestCaseMixin,
lrouter = idlutils.row_by_value(
self.nb_api.idl, 'Logical_Router', 'name',
'neutron-' + router_id, None)
lports = getattr(lrouter, 'ports', [])
all_lports = getattr(lrouter, 'ports', [])
managed_lports = [
lport for lport in all_lports
if (ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY in
lport.external_ids)
]
monitor_lrouter_port_ids = [lport.name.replace('lrp-', '')
for lport in lports]
for lport in managed_lports]
monitor_lport_networks = {
lport.name.replace('lrp-', ''): lport.networks
for lport in lports}
for lport in managed_lports}
monitor_lport_ra_configs = {
lport.name.replace('lrp-', ''): lport.ipv6_ra_configs
for lport in lports}
for lport in managed_lports}
sroutes = getattr(lrouter, 'static_routes', [])
monitor_routes = [sroute.ip_prefix + sroute.nexthop
for sroute in sroutes]
monitor_routes = []
for sroute in sroutes:
if any(e_id.startswith(neutron_prefix)
for e_id in sroute.external_ids):
monitor_routes.append(
sroute.ip_prefix + sroute.nexthop
)
nats = getattr(lrouter, 'nat', [])
monitor_nats = [
nat.external_ip + nat.logical_ip + nat.type +
@@ -1522,7 +1576,9 @@ class TestOvnNbSync(testlib_api.MySQLTestCaseMixin,
mn_pgs = []
for row in self.nb_api.tables['Port_Group'].rows.values():
mn_pgs.append(getattr(row, 'name', ''))
if (ovn_const.OVN_SG_EXT_ID_KEY in row.external_ids or
row.name == ovn_const.OVN_DROP_PORT_GROUP_NAME):
mn_pgs.append(getattr(row, 'name', ''))
if should_match:
self.assertCountEqual(nb_pgs, db_pgs)
@@ -1628,6 +1684,46 @@ class TestOvnNbSync(testlib_api.MySQLTestCaseMixin,
self._sync_resources(mode)
self._validate_resources(should_match=should_match_after_sync)
if not restart_ovsdb_processes:
# Restarting ovsdb-server removes all its previous content.
# We can not expect to find external resources in the DB
# if it was wiped out.
self._validate_external_resources()
def _validate_external_resources(self):
"""Ensure that resources not owned by Neutron are in the OVN DB.
This function is useful to validate that external resources survived
ovn_db_sync.
"""
db_routers = self._list('routers')
db_router_ids = [router['id'] for router in db_routers['routers']]
pgs = []
for pg in self.nb_api.tables['Port_Group'].rows.values():
pgs.append(pg.name)
lrports = []
sroutes = []
for router_id in db_router_ids:
lrouter = idlutils.row_by_value(
self.mech_driver.nb_ovn.idl, 'Logical_Router', 'name',
'neutron-' + str(router_id), None)
for lrport in getattr(lrouter, 'ports', []):
lrports.append(lrport.name)
for route in getattr(lrouter, 'static_routes', []):
sroutes.append(route.ip_prefix + route.nexthop)
for port_name, _ in self.create_ext_lrouter_ports:
self.assertIn(port_name, lrports)
for _, prefix, next_hop in self.create_ext_lrouter_routes:
self.assertIn(prefix + next_hop, sroutes)
for ext_pg in self.create_ext_port_groups:
self.assertIn(ext_pg['name'], pgs)
def test_ovn_nb_sync_repair(self):
self._test_ovn_nb_sync_helper(ovn_const.OVN_DB_SYNC_MODE_REPAIR)

View File

@@ -189,12 +189,21 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
'networks': ['10.0.3.0/24'],
'options': {ovn_const.OVN_GATEWAY_CHASSIS_KEY: None}},
{'name': 'xrp-id-b1',
'external_ids': {}, 'networks': ['20.0.1.0/24']},
'external_ids': {
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
utils.ovn_name('lr-id-b'),
}, 'networks': ['20.0.1.0/24']},
{'name': utils.ovn_lrouter_port_name('orp-id-b2'),
'external_ids': {}, 'networks': ['20.0.2.0/24'],
'external_ids': {
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
utils.ovn_name('lr-id-b'),
}, 'networks': ['20.0.2.0/24'],
'options': {ovn_const.OVN_GATEWAY_CHASSIS_KEY: 'host-2'}},
{'name': utils.ovn_lrouter_port_name('orp-id-b3'),
'external_ids': {}, 'networks': ['20.0.3.0/24'],
'external_ids': {
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
utils.ovn_name('lr-id-b'),
}, 'networks': ['20.0.3.0/24'],
'options': {}},
{'name': utils.ovn_lrouter_port_name('gwc'),
'external_ids': {
@@ -202,14 +211,27 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
utils.ovn_name('lr-id-f'),
ovn_const.OVN_ROUTER_IS_EXT_GW: str(True)},
'networks': ['10.0.4.0/24'],
'options': {}},
{'name': utils.ovn_lrouter_port_name('not-managed'),
'external_ids': {
'owner': 'not-owned-by-neutron',
},
'networks': ['10.0.5.0/24'],
'options': {}}],
'gateway_chassis': [
{'chassis_name': 'fake-chassis',
'name': utils.ovn_lrouter_port_name('gwc') + '_fake-chassis'}],
'static_routes': [{'ip_prefix': '20.0.0.0/16',
'nexthop': '10.0.3.253'},
'nexthop': '10.0.3.253',
'external_ids': {
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'uuid_1'}},
{'ip_prefix': '10.0.0.0/16',
'nexthop': '20.0.2.253'}],
'nexthop': '20.0.2.253',
'external_ids': {
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'uuid_2'}},
{'ip_prefix': '30.0.0.0/16',
'nexthop': '30.0.4.253',
'external_ids': {'owner': 'not-owned-by-neutron'}}],
'nats': [{'external_ip': '10.0.3.1', 'logical_ip': '20.0.0.0/16',
'type': 'snat'},
{'external_ip': '20.0.2.1', 'logical_ip': '10.0.0.0/24',
@@ -489,7 +511,7 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
def _test_get_all_logical_routers_with_rports(self, is_gw_port):
# Test empty
mapping = self.nb_ovn_idl.get_all_logical_switches_with_ports()
mapping = self.nb_ovn_idl.get_all_logical_routers_with_rports()
self.assertCountEqual(mapping, {})
# Test loaded values
self._load_nb_db()

View File

@@ -428,30 +428,39 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
attrs={'name': 'ls0',
'other_config': {
constants.MCAST_SNOOP: 'false',
constants.MCAST_FLOOD_UNREGISTERED: 'false'}})
constants.MCAST_FLOOD_UNREGISTERED: 'false'},
'external_ids': {constants.OVN_NETWORK_NAME_EXT_ID_KEY: 'port0'}})
ls1 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'name': 'ls1',
'other_config': {}})
'other_config': {},
'external_ids': {constants.OVN_NETWORK_NAME_EXT_ID_KEY: 'port1'}})
ls2 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'name': 'ls2',
'other_config': {
constants.MCAST_SNOOP: 'true',
constants.MCAST_FLOOD_UNREGISTERED: 'false'}})
constants.MCAST_FLOOD_UNREGISTERED: 'false'},
'external_ids': {constants.OVN_NETWORK_NAME_EXT_ID_KEY: 'port2'}})
ls3 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'name': '',
'other_config': {}})
'other_config': {},
'external_ids': {constants.OVN_NETWORK_NAME_EXT_ID_KEY: 'port3'}})
ls4 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'name': '',
'other_config': {constants.MCAST_SNOOP: 'false'}})
'other_config': {constants.MCAST_SNOOP: 'false'},
'external_ids': {constants.OVN_NETWORK_NAME_EXT_ID_KEY: 'port4'}})
ls5 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'name': 'ls5',
'other_config': {},
'external_ids': {}})
nb_idl.ls_list.return_value.execute.return_value = [ls0, ls1, ls2, ls3,
ls4]
ls4, ls5]
self.assertRaises(periodics.NeverAgain,
self.periodic.check_for_igmp_snoop_support)
# "ls2" is not part of the transaction because it already
# have the right value set; "ls3" and "ls4" do not have a name set.
# have the right value set; "ls3" and "ls4" do not have a name set;
# "ls5" is not managed by neutron.
expected_calls = [
mock.call('Logical_Switch', 'ls0',
('other_config', {
@@ -506,7 +515,14 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
'external_ids': {
constants.OVN_NETWORK_NAME_EXT_ID_KEY: 'neutron-net1'}})
nb_idl.db_find_rows.return_value.execute.return_value = [p0, p1]
# Port p2 is not owned by Neutron and should not be affected.
p2 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'type': constants.LSP_TYPE_EXTERNAL,
'name': 'p2',
'ha_chassis_group': [hcg1],
'external_ids': {}})
nb_idl.db_find_rows.return_value.execute.return_value = [p0, p1, p2]
mock_sync_ha_chassis_group_network.return_value = hcg0.uuid, mock.ANY
# Invoke the periodic method, it meant to run only once at startup

View File

@@ -147,7 +147,7 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
'security_group_id': 'sg2'}],
'name': 'all-tcpe'}]
self.sg_port_groups_ovn = [mock.Mock(), mock.Mock(), mock.Mock()]
self.sg_port_groups_ovn = [mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock()]
self.sg_port_groups_ovn[0].configure_mock(
name='pg_sg1',
external_ids={ovn_const.OVN_SG_EXT_ID_KEY: 'sg1'},
@@ -159,8 +159,13 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
ports=[],
acls=[])
self.sg_port_groups_ovn[2].configure_mock(
name='neutron_pg_drop',
external_ids=[],
name=ovn_const.OVN_DROP_PORT_GROUP_NAME,
external_ids={},
ports=[],
acls=[])
self.sg_port_groups_ovn[3].configure_mock(
name='external_pg',
external_ids={'owner': 'not-owned-by-neutron'},
ports=[],
acls=[])