Skip to content

Commit 52f47c0

Browse files
author
Luca Czesla
committed
Add address scope to the OVN LSP port registers
To be able to filter on the address scope of the ports in the ovn-bgp-agent we add the address scope of the subnet pool to each LSP port in the northbound. Northd writes it to the southbound so the ovn-bgp-agent has access to it. The ovn-bgp-agent talks directly to the southbound to announce the networks via BGP that match the configured address scope. Change-Id: Ieb4cab11068747bd79868c6eb1778aab91447c30 Closes-Bug: #1996741 (cherry picked from commit 2272b9b)
1 parent 25dc4c0 commit 52f47c0

File tree

6 files changed

+238
-22
lines changed

6 files changed

+238
-22
lines changed

neutron/common/ovn/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
OVN_GW_PORT_EXT_ID_KEY = 'neutron:gw_port_id'
3232
OVN_SUBNET_EXT_ID_KEY = 'neutron:subnet_id'
3333
OVN_SUBNET_EXT_IDS_KEY = 'neutron:subnet_ids'
34+
OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY = 'neutron:subnet_pool_addr_scope4'
35+
OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY = 'neutron:subnet_pool_addr_scope6'
3436
OVN_PHYSNET_EXT_ID_KEY = 'neutron:provnet-physical-network'
3537
OVN_NETTYPE_EXT_ID_KEY = 'neutron:provnet-network-type'
3638
OVN_SEGID_EXT_ID_KEY = 'neutron:provnet-segmentation-id'

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,47 @@ def check_for_igmp_snoop_support(self):
596596

597597
raise periodics.NeverAgain()
598598

599+
# TODO(czesla): Remove this in the A+4 cycle
600+
# A static spacing value is used here, but this method will only run
601+
# once per lock due to the use of periodics.NeverAgain().
602+
@periodics.periodic(spacing=600, run_immediately=True)
603+
def check_port_has_address_scope(self):
604+
if not self.has_lock:
605+
return
606+
607+
ports = self._nb_idl.db_find_rows(
608+
"Logical_Switch_Port", ("type", "!=", ovn_const.LSP_TYPE_LOCALNET)
609+
).execute(check_error=True)
610+
611+
context = n_context.get_admin_context()
612+
with self._nb_idl.transaction(check_error=True) as txn:
613+
for port in ports:
614+
if (
615+
port.external_ids.get(
616+
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY
617+
) is None or
618+
port.external_ids.get(
619+
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY
620+
) is None
621+
):
622+
try:
623+
port_neutron = self._ovn_client._plugin.get_port(
624+
context, port.name
625+
)
626+
627+
port_info, external_ids = (
628+
self._ovn_client.get_external_ids_from_port(
629+
port_neutron)
630+
)
631+
txn.add(self._nb_idl.set_lswitch_port(
632+
port.name, external_ids=external_ids))
633+
except n_exc.PortNotFound:
634+
# The sync function will fix this port
635+
pass
636+
except Exception:
637+
LOG.exception('Failed to update port %s', port.name)
638+
raise periodics.NeverAgain()
639+
599640
def _delete_default_ha_chassis_group(self, txn):
600641
# TODO(lucasgomes): Remove the deletion of the
601642
# HA_CHASSIS_GROUP_DEFAULT_NAME in the Y cycle. We no longer

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

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,23 @@
5858

5959

6060
OvnPortInfo = collections.namedtuple(
61-
'OvnPortInfo', ['type', 'options', 'addresses', 'port_security',
62-
'parent_name', 'tag', 'dhcpv4_options', 'dhcpv6_options',
63-
'cidrs', 'device_owner', 'security_group_ids'])
61+
"OvnPortInfo",
62+
[
63+
"type",
64+
"options",
65+
"addresses",
66+
"port_security",
67+
"parent_name",
68+
"tag",
69+
"dhcpv4_options",
70+
"dhcpv6_options",
71+
"cidrs",
72+
"device_owner",
73+
"security_group_ids",
74+
"address4_scope_id",
75+
"address6_scope_id",
76+
],
77+
)
6478

6579

6680
GW_INFO = collections.namedtuple('GatewayInfo', ['network_id', 'subnet_id',
@@ -264,6 +278,8 @@ def _get_port_options(self, port):
264278

265279
port_type = ''
266280
cidrs = ''
281+
address4_scope_id = ""
282+
address6_scope_id = ""
267283
dhcpv4_options = self._get_port_dhcp_options(port, const.IP_VERSION_4)
268284
dhcpv6_options = self._get_port_dhcp_options(port, const.IP_VERSION_6)
269285
if vtep_physical_switch:
@@ -306,6 +322,26 @@ def _get_port_options(self, port):
306322
ip_addr)
307323
continue
308324

325+
if subnet["subnetpool_id"]:
326+
try:
327+
subnet_pool = self._plugin.get_subnetpool(
328+
context, id=subnet["subnetpool_id"]
329+
)
330+
if subnet_pool["address_scope_id"]:
331+
ip_version = subnet_pool["ip_version"]
332+
if ip_version == const.IP_VERSION_4:
333+
address4_scope_id = subnet_pool[
334+
"address_scope_id"
335+
]
336+
elif ip_version == const.IP_VERSION_6:
337+
address6_scope_id = subnet_pool[
338+
"address_scope_id"
339+
]
340+
except n_exc.SubnetPoolNotFound:
341+
# swallow the exception and just continue if the
342+
# lookup failed
343+
pass
344+
309345
cidrs += ' {}/{}'.format(ip['ip_address'],
310346
subnet['cidr'].split('/')[1])
311347

@@ -403,7 +439,9 @@ def _get_port_options(self, port):
403439
sg_ids = ' '.join(utils.get_lsp_security_groups(port))
404440
return OvnPortInfo(port_type, options, addresses, port_security,
405441
parent_name, tag, dhcpv4_options, dhcpv6_options,
406-
cidrs.strip(), device_owner, sg_ids)
442+
cidrs.strip(), device_owner, sg_ids,
443+
address4_scope_id, address6_scope_id
444+
)
407445

408446
def sync_ha_chassis_group(self, context, network_id, txn):
409447
"""Return the UUID of the HA Chassis Group.
@@ -474,24 +512,32 @@ def sync_ha_chassis_group(self, context, network_id, txn):
474512

475513
return ha_ch_grp.uuid
476514

477-
def create_port(self, context, port):
478-
if utils.is_lsp_ignored(port):
479-
return
480-
515+
def get_external_ids_from_port(self, port):
481516
port_info = self._get_port_options(port)
482517
external_ids = {ovn_const.OVN_PORT_NAME_EXT_ID_KEY: port['name'],
483518
ovn_const.OVN_DEVID_EXT_ID_KEY: port['device_id'],
484519
ovn_const.OVN_PROJID_EXT_ID_KEY: port['project_id'],
485520
ovn_const.OVN_CIDRS_EXT_ID_KEY: port_info.cidrs,
486521
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
487522
port_info.device_owner,
523+
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY:
524+
port_info.address4_scope_id,
525+
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY:
526+
port_info.address6_scope_id,
488527
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
489528
utils.ovn_name(port['network_id']),
490529
ovn_const.OVN_SG_IDS_EXT_ID_KEY:
491530
port_info.security_group_ids,
492531
ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(
493532
utils.get_revision_number(
494533
port, ovn_const.TYPE_PORTS))}
534+
return port_info, external_ids
535+
536+
def create_port(self, context, port):
537+
if utils.is_lsp_ignored(port):
538+
return
539+
540+
port_info, external_ids = self.get_external_ids_from_port(port)
495541
lswitch_name = utils.ovn_name(port['network_id'])
496542

497543
# It's possible to have a network created on one controller and then a
@@ -607,20 +653,8 @@ def _set_unset_virtual_port_type(self, context, txn, parent_port,
607653
def update_port(self, context, port, port_object=None):
608654
if utils.is_lsp_ignored(port):
609655
return
610-
port_info = self._get_port_options(port)
611-
external_ids = {ovn_const.OVN_PORT_NAME_EXT_ID_KEY: port['name'],
612-
ovn_const.OVN_DEVID_EXT_ID_KEY: port['device_id'],
613-
ovn_const.OVN_PROJID_EXT_ID_KEY: port['project_id'],
614-
ovn_const.OVN_CIDRS_EXT_ID_KEY: port_info.cidrs,
615-
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
616-
port_info.device_owner,
617-
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
618-
utils.ovn_name(port['network_id']),
619-
ovn_const.OVN_SG_IDS_EXT_ID_KEY:
620-
port_info.security_group_ids,
621-
ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(
622-
utils.get_revision_number(
623-
port, ovn_const.TYPE_PORTS))}
656+
657+
port_info, external_ids = self.get_external_ids_from_port(port)
624658

625659
check_rev_cmd = self._nb_idl.check_revision_number(
626660
port['id'], port, ovn_const.TYPE_PORTS)

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from neutron.tests.unit import fake_resources as fakes
3131
from neutron.tests.unit.plugins.ml2 import test_security_group as test_sg
3232
from neutron.tests.unit import testlib_api
33+
from neutron_lib import exceptions as n_exc
3334

3435

3536
class TestSchemaAwarePeriodicsBase(testlib_api.SqlTestCaseLight):
@@ -444,6 +445,82 @@ def test_check_for_ha_chassis_group(self):
444445
nb_idl.set_lswitch_port.assert_called_once_with(
445446
'p1', ha_chassis_group=hcg0.uuid)
446447

448+
def test_check_port_has_address_scope(self):
449+
self.fake_ovn_client.is_external_ports_supported.return_value = True
450+
nb_idl = self.fake_ovn_client._nb_idl
451+
452+
# Already has the address scope set but empty, nothing to do
453+
lsp0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
454+
attrs={
455+
"uuid": "1f4323db-fb58-48e9-adae-6c6e833c581f",
456+
"name": "lsp0",
457+
"external_ids": {
458+
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY: "",
459+
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY: "",
460+
},
461+
}
462+
)
463+
464+
# address scope is missing, needs update
465+
lsp1 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
466+
attrs={
467+
"uuid": "1f4323db-fb58-48e9-adae-6c6e833c581d",
468+
"name": "lsp1",
469+
"external_ids": {},
470+
}
471+
)
472+
473+
# Already has the address scope set, nothing to do
474+
lsp2 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
475+
attrs={
476+
"uuid": "1f4323db-fb58-48e9-adae-6c6e833c581a",
477+
"name": "lsp2",
478+
"external_ids": {
479+
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY: "fakev4",
480+
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY: "fakev6",
481+
},
482+
}
483+
)
484+
485+
# address scope is missing, needs update but port is missing in ovn
486+
lsp4 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
487+
attrs={
488+
"uuid": "1f4323db-fb58-48e9-adae-6c6e833c581c",
489+
"name": "lsp4",
490+
"external_ids": {},
491+
}
492+
)
493+
494+
nb_idl.db_find_rows.return_value.execute.return_value = [
495+
lsp0,
496+
lsp1,
497+
lsp2,
498+
lsp4,
499+
]
500+
501+
self.fake_ovn_client._plugin.get_port.side_effect = [
502+
{"network_id": "net0"},
503+
n_exc.PortNotFound(port_id="port"),
504+
]
505+
506+
external_ids = {
507+
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY: "address_scope_v4",
508+
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY: "address_scope_v6",
509+
}
510+
511+
self.fake_ovn_client.get_external_ids_from_port.return_value = (
512+
None,
513+
external_ids,
514+
)
515+
516+
self.assertRaises(
517+
periodics.NeverAgain, self.periodic.check_port_has_address_scope
518+
)
519+
520+
nb_idl.set_lswitch_port.assert_called_once_with(
521+
"lsp1", external_ids=external_ids
522+
)
523+
447524
def test_check_for_mcast_flood_reports(self):
448525
nb_idl = self.fake_ovn_client._nb_idl
449526
lsp0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,6 +1791,62 @@ def test__get_port_options(self):
17911791
mock.ANY,
17921792
filters={'id': subnet_ids})
17931793

1794+
def test__get_port_options_with_addr_scope(self):
1795+
with mock.patch.object(
1796+
self.mech_driver._plugin, "get_subnets"
1797+
) as mock_get_subnets, mock.patch.object(
1798+
self.mech_driver._plugin,
1799+
"get_subnetpool",
1800+
) as mock_get_subnetpool:
1801+
port = {
1802+
"id": "virt-port",
1803+
"mac_address": "00:00:00:00:00:00",
1804+
"device_owner": "device_owner",
1805+
"network_id": "foo",
1806+
"fixed_ips": [
1807+
{"subnet_id": "subnet-1", "ip_address": "10.0.0.55"},
1808+
{"subnet_id": "subnet-2", "ip_address": "aef0::4"},
1809+
],
1810+
}
1811+
1812+
subnet_ids = [ip["subnet_id"] for ip in port.get("fixed_ips")]
1813+
mock_get_subnets.return_value = [
1814+
{
1815+
"id": "subnet-1",
1816+
"subnetpool_id": "subnetpool1",
1817+
"cidr": "10.0.0.0/24",
1818+
},
1819+
{
1820+
"id": "subnet-2",
1821+
"subnetpool_id": "subnetpool2",
1822+
"cidr": "aef0::/64",
1823+
},
1824+
]
1825+
mock_get_subnetpool.side_effect = [
1826+
{
1827+
"ip_version": const.IP_VERSION_4,
1828+
"address_scope_id": "address_scope_v4",
1829+
},
1830+
{
1831+
"ip_version": const.IP_VERSION_6,
1832+
"address_scope_id": "address_scope_v6",
1833+
},
1834+
]
1835+
options = self.mech_driver._ovn_client._get_port_options(port)
1836+
mock_get_subnets.assert_called_once_with(
1837+
mock.ANY, filters={"id": subnet_ids}
1838+
)
1839+
1840+
expected_calls = [
1841+
mock.call(mock.ANY, id="subnetpool1"),
1842+
mock.call(mock.ANY, id="subnetpool2"),
1843+
]
1844+
1845+
mock_get_subnetpool.assert_has_calls(expected_calls)
1846+
1847+
self.assertEqual("address_scope_v4", options.address4_scope_id)
1848+
self.assertEqual("address_scope_v6", options.address6_scope_id)
1849+
17941850
def test__get_port_options_migrating_additional_chassis_missing(self):
17951851
port = {
17961852
'id': 'virt-port',
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
features:
3+
- |
4+
Address scope is now added to all OVN LSP port registers in the
5+
northbound. Northd then writes the address scope from the northbound to
6+
the southbound so it can be used there by the ovn-bgp-agent.

0 commit comments

Comments
 (0)