Skip to content

Commit da4096d

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add address scope to the OVN LSP port registers" into stable/yoga
2 parents a4b8c97 + 52f47c0 commit da4096d

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
@@ -599,6 +599,47 @@ def check_for_igmp_snoop_support(self):
599599

600600
raise periodics.NeverAgain()
601601

602+
# TODO(czesla): Remove this in the A+4 cycle
603+
# A static spacing value is used here, but this method will only run
604+
# once per lock due to the use of periodics.NeverAgain().
605+
@periodics.periodic(spacing=600, run_immediately=True)
606+
def check_port_has_address_scope(self):
607+
if not self.has_lock:
608+
return
609+
610+
ports = self._nb_idl.db_find_rows(
611+
"Logical_Switch_Port", ("type", "!=", ovn_const.LSP_TYPE_LOCALNET)
612+
).execute(check_error=True)
613+
614+
context = n_context.get_admin_context()
615+
with self._nb_idl.transaction(check_error=True) as txn:
616+
for port in ports:
617+
if (
618+
port.external_ids.get(
619+
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY
620+
) is None or
621+
port.external_ids.get(
622+
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY
623+
) is None
624+
):
625+
try:
626+
port_neutron = self._ovn_client._plugin.get_port(
627+
context, port.name
628+
)
629+
630+
port_info, external_ids = (
631+
self._ovn_client.get_external_ids_from_port(
632+
port_neutron)
633+
)
634+
txn.add(self._nb_idl.set_lswitch_port(
635+
port.name, external_ids=external_ids))
636+
except n_exc.PortNotFound:
637+
# The sync function will fix this port
638+
pass
639+
except Exception:
640+
LOG.exception('Failed to update port %s', port.name)
641+
raise periodics.NeverAgain()
642+
602643
def _delete_default_ha_chassis_group(self, txn):
603644
# TODO(lucasgomes): Remove the deletion of the
604645
# 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
@@ -1792,6 +1792,62 @@ def test__get_port_options(self):
17921792
mock.ANY,
17931793
filters={'id': subnet_ids})
17941794

1795+
def test__get_port_options_with_addr_scope(self):
1796+
with mock.patch.object(
1797+
self.mech_driver._plugin, "get_subnets"
1798+
) as mock_get_subnets, mock.patch.object(
1799+
self.mech_driver._plugin,
1800+
"get_subnetpool",
1801+
) as mock_get_subnetpool:
1802+
port = {
1803+
"id": "virt-port",
1804+
"mac_address": "00:00:00:00:00:00",
1805+
"device_owner": "device_owner",
1806+
"network_id": "foo",
1807+
"fixed_ips": [
1808+
{"subnet_id": "subnet-1", "ip_address": "10.0.0.55"},
1809+
{"subnet_id": "subnet-2", "ip_address": "aef0::4"},
1810+
],
1811+
}
1812+
1813+
subnet_ids = [ip["subnet_id"] for ip in port.get("fixed_ips")]
1814+
mock_get_subnets.return_value = [
1815+
{
1816+
"id": "subnet-1",
1817+
"subnetpool_id": "subnetpool1",
1818+
"cidr": "10.0.0.0/24",
1819+
},
1820+
{
1821+
"id": "subnet-2",
1822+
"subnetpool_id": "subnetpool2",
1823+
"cidr": "aef0::/64",
1824+
},
1825+
]
1826+
mock_get_subnetpool.side_effect = [
1827+
{
1828+
"ip_version": const.IP_VERSION_4,
1829+
"address_scope_id": "address_scope_v4",
1830+
},
1831+
{
1832+
"ip_version": const.IP_VERSION_6,
1833+
"address_scope_id": "address_scope_v6",
1834+
},
1835+
]
1836+
options = self.mech_driver._ovn_client._get_port_options(port)
1837+
mock_get_subnets.assert_called_once_with(
1838+
mock.ANY, filters={"id": subnet_ids}
1839+
)
1840+
1841+
expected_calls = [
1842+
mock.call(mock.ANY, id="subnetpool1"),
1843+
mock.call(mock.ANY, id="subnetpool2"),
1844+
]
1845+
1846+
mock_get_subnetpool.assert_has_calls(expected_calls)
1847+
1848+
self.assertEqual("address_scope_v4", options.address4_scope_id)
1849+
self.assertEqual("address_scope_v6", options.address6_scope_id)
1850+
17951851
def test__get_port_options_migrating_additional_chassis_missing(self):
17961852
port = {
17971853
'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)