Merge "Add address scope to the OVN LSP port registers"

This commit is contained in:
Zuul 2022-12-07 13:41:00 +00:00 committed by Gerrit Code Review
commit fc3868ca2d
6 changed files with 238 additions and 22 deletions

View File

@ -34,6 +34,8 @@ OVN_GW_PORT_EXT_ID_KEY = 'neutron:gw_port_id'
OVN_GW_NETWORK_EXT_ID_KEY = 'neutron:gw_network_id'
OVN_SUBNET_EXT_ID_KEY = 'neutron:subnet_id'
OVN_SUBNET_EXT_IDS_KEY = 'neutron:subnet_ids'
OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY = 'neutron:subnet_pool_addr_scope4'
OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY = 'neutron:subnet_pool_addr_scope6'
OVN_PHYSNET_EXT_ID_KEY = 'neutron:provnet-physical-network'
OVN_NETTYPE_EXT_ID_KEY = 'neutron:provnet-network-type'
OVN_SEGID_EXT_ID_KEY = 'neutron:provnet-segmentation-id'

View File

@ -532,6 +532,47 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
raise periodics.NeverAgain()
# TODO(czesla): Remove this in the A+4 cycle
# A static spacing value is used here, but this method will only run
# once per lock due to the use of periodics.NeverAgain().
@periodics.periodic(spacing=600, run_immediately=True)
def check_port_has_address_scope(self):
if not self.has_lock:
return
ports = self._nb_idl.db_find_rows(
"Logical_Switch_Port", ("type", "!=", ovn_const.LSP_TYPE_LOCALNET)
).execute(check_error=True)
context = n_context.get_admin_context()
with self._nb_idl.transaction(check_error=True) as txn:
for port in ports:
if (
port.external_ids.get(
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY
) is None or
port.external_ids.get(
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY
) is None
):
try:
port_neutron = self._ovn_client._plugin.get_port(
context, port.name
)
port_info, external_ids = (
self._ovn_client.get_external_ids_from_port(
port_neutron)
)
txn.add(self._nb_idl.set_lswitch_port(
port.name, external_ids=external_ids))
except n_exc.PortNotFound:
# The sync function will fix this port
pass
except Exception:
LOG.exception('Failed to update port %s', port.name)
raise periodics.NeverAgain()
def _delete_default_ha_chassis_group(self, txn):
# TODO(lucasgomes): Remove the deletion of the
# HA_CHASSIS_GROUP_DEFAULT_NAME in the Y cycle. We no longer

View File

@ -58,9 +58,23 @@ LOG = log.getLogger(__name__)
OvnPortInfo = collections.namedtuple(
'OvnPortInfo', ['type', 'options', 'addresses', 'port_security',
'parent_name', 'tag', 'dhcpv4_options', 'dhcpv6_options',
'cidrs', 'device_owner', 'security_group_ids'])
"OvnPortInfo",
[
"type",
"options",
"addresses",
"port_security",
"parent_name",
"tag",
"dhcpv4_options",
"dhcpv6_options",
"cidrs",
"device_owner",
"security_group_ids",
"address4_scope_id",
"address6_scope_id",
],
)
GW_INFO = collections.namedtuple('GatewayInfo', ['network_id', 'subnet_id',
@ -264,6 +278,8 @@ class OVNClient(object):
port_type = ''
cidrs = ''
address4_scope_id = ""
address6_scope_id = ""
dhcpv4_options = self._get_port_dhcp_options(port, const.IP_VERSION_4)
dhcpv6_options = self._get_port_dhcp_options(port, const.IP_VERSION_6)
if vtep_physical_switch:
@ -306,6 +322,26 @@ class OVNClient(object):
ip_addr)
continue
if subnet["subnetpool_id"]:
try:
subnet_pool = self._plugin.get_subnetpool(
context, id=subnet["subnetpool_id"]
)
if subnet_pool["address_scope_id"]:
ip_version = subnet_pool["ip_version"]
if ip_version == const.IP_VERSION_4:
address4_scope_id = subnet_pool[
"address_scope_id"
]
elif ip_version == const.IP_VERSION_6:
address6_scope_id = subnet_pool[
"address_scope_id"
]
except n_exc.SubnetPoolNotFound:
# swallow the exception and just continue if the
# lookup failed
pass
cidrs += ' {}/{}'.format(ip['ip_address'],
subnet['cidr'].split('/')[1])
@ -403,7 +439,9 @@ class OVNClient(object):
sg_ids = ' '.join(utils.get_lsp_security_groups(port))
return OvnPortInfo(port_type, options, addresses, port_security,
parent_name, tag, dhcpv4_options, dhcpv6_options,
cidrs.strip(), device_owner, sg_ids)
cidrs.strip(), device_owner, sg_ids,
address4_scope_id, address6_scope_id
)
def sync_ha_chassis_group(self, context, network_id, txn):
"""Return the UUID of the HA Chassis Group.
@ -493,10 +531,7 @@ class OVNClient(object):
return (dhcpv4_options, dhcpv6_options)
def create_port(self, context, port):
if utils.is_lsp_ignored(port):
return
def get_external_ids_from_port(self, port):
port_info = self._get_port_options(port)
external_ids = {ovn_const.OVN_PORT_NAME_EXT_ID_KEY: port['name'],
ovn_const.OVN_DEVID_EXT_ID_KEY: port['device_id'],
@ -504,6 +539,10 @@ class OVNClient(object):
ovn_const.OVN_CIDRS_EXT_ID_KEY: port_info.cidrs,
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
port_info.device_owner,
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY:
port_info.address4_scope_id,
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY:
port_info.address6_scope_id,
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
utils.ovn_name(port['network_id']),
ovn_const.OVN_SG_IDS_EXT_ID_KEY:
@ -511,6 +550,13 @@ class OVNClient(object):
ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(
utils.get_revision_number(
port, ovn_const.TYPE_PORTS))}
return port_info, external_ids
def create_port(self, context, port):
if utils.is_lsp_ignored(port):
return
port_info, external_ids = self.get_external_ids_from_port(port)
lswitch_name = utils.ovn_name(port['network_id'])
# It's possible to have a network created on one controller and then a
@ -616,20 +662,8 @@ class OVNClient(object):
def update_port(self, context, port, port_object=None):
if utils.is_lsp_ignored(port):
return
port_info = self._get_port_options(port)
external_ids = {ovn_const.OVN_PORT_NAME_EXT_ID_KEY: port['name'],
ovn_const.OVN_DEVID_EXT_ID_KEY: port['device_id'],
ovn_const.OVN_PROJID_EXT_ID_KEY: port['project_id'],
ovn_const.OVN_CIDRS_EXT_ID_KEY: port_info.cidrs,
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
port_info.device_owner,
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
utils.ovn_name(port['network_id']),
ovn_const.OVN_SG_IDS_EXT_ID_KEY:
port_info.security_group_ids,
ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(
utils.get_revision_number(
port, ovn_const.TYPE_PORTS))}
port_info, external_ids = self.get_external_ids_from_port(port)
check_rev_cmd = self._nb_idl.check_revision_number(
port['id'], port, ovn_const.TYPE_PORTS)

View File

@ -31,6 +31,7 @@ from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_db_sync
from neutron.tests.unit import fake_resources as fakes
from neutron.tests.unit.plugins.ml2 import test_security_group as test_sg
from neutron.tests.unit import testlib_api
from neutron_lib import exceptions as n_exc
class TestSchemaAwarePeriodicsBase(testlib_api.SqlTestCaseLight):
@ -456,6 +457,82 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
nb_idl.set_lswitch_port.assert_called_once_with(
'p1', ha_chassis_group=hcg0.uuid)
def test_check_port_has_address_scope(self):
self.fake_ovn_client.is_external_ports_supported.return_value = True
nb_idl = self.fake_ovn_client._nb_idl
# Already has the address scope set but empty, nothing to do
lsp0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={
"uuid": "1f4323db-fb58-48e9-adae-6c6e833c581f",
"name": "lsp0",
"external_ids": {
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY: "",
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY: "",
},
}
)
# address scope is missing, needs update
lsp1 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={
"uuid": "1f4323db-fb58-48e9-adae-6c6e833c581d",
"name": "lsp1",
"external_ids": {},
}
)
# Already has the address scope set, nothing to do
lsp2 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={
"uuid": "1f4323db-fb58-48e9-adae-6c6e833c581a",
"name": "lsp2",
"external_ids": {
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY: "fakev4",
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY: "fakev6",
},
}
)
# address scope is missing, needs update but port is missing in ovn
lsp4 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={
"uuid": "1f4323db-fb58-48e9-adae-6c6e833c581c",
"name": "lsp4",
"external_ids": {},
}
)
nb_idl.db_find_rows.return_value.execute.return_value = [
lsp0,
lsp1,
lsp2,
lsp4,
]
self.fake_ovn_client._plugin.get_port.side_effect = [
{"network_id": "net0"},
n_exc.PortNotFound(port_id="port"),
]
external_ids = {
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY: "address_scope_v4",
constants.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY: "address_scope_v6",
}
self.fake_ovn_client.get_external_ids_from_port.return_value = (
None,
external_ids,
)
self.assertRaises(
periodics.NeverAgain, self.periodic.check_port_has_address_scope
)
nb_idl.set_lswitch_port.assert_called_once_with(
"lsp1", external_ids=external_ids
)
def test_check_for_mcast_flood_reports(self):
nb_idl = self.fake_ovn_client._nb_idl
lsp0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(

View File

@ -1810,6 +1810,62 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
mock.ANY,
filters={'id': subnet_ids})
def test__get_port_options_with_addr_scope(self):
with mock.patch.object(
self.mech_driver._plugin, "get_subnets"
) as mock_get_subnets, mock.patch.object(
self.mech_driver._plugin,
"get_subnetpool",
) as mock_get_subnetpool:
port = {
"id": "virt-port",
"mac_address": "00:00:00:00:00:00",
"device_owner": "device_owner",
"network_id": "foo",
"fixed_ips": [
{"subnet_id": "subnet-1", "ip_address": "10.0.0.55"},
{"subnet_id": "subnet-2", "ip_address": "aef0::4"},
],
}
subnet_ids = [ip["subnet_id"] for ip in port.get("fixed_ips")]
mock_get_subnets.return_value = [
{
"id": "subnet-1",
"subnetpool_id": "subnetpool1",
"cidr": "10.0.0.0/24",
},
{
"id": "subnet-2",
"subnetpool_id": "subnetpool2",
"cidr": "aef0::/64",
},
]
mock_get_subnetpool.side_effect = [
{
"ip_version": const.IP_VERSION_4,
"address_scope_id": "address_scope_v4",
},
{
"ip_version": const.IP_VERSION_6,
"address_scope_id": "address_scope_v6",
},
]
options = self.mech_driver._ovn_client._get_port_options(port)
mock_get_subnets.assert_called_once_with(
mock.ANY, filters={"id": subnet_ids}
)
expected_calls = [
mock.call(mock.ANY, id="subnetpool1"),
mock.call(mock.ANY, id="subnetpool2"),
]
mock_get_subnetpool.assert_has_calls(expected_calls)
self.assertEqual("address_scope_v4", options.address4_scope_id)
self.assertEqual("address_scope_v6", options.address6_scope_id)
def test__get_port_options_migrating_additional_chassis_missing(self):
port = {
'id': 'virt-port',

View File

@ -0,0 +1,6 @@
---
features:
- |
Address scope is now added to all OVN LSP port registers in the
northbound. Northd then writes the address scope from the northbound to
the southbound so it can be used there by the ovn-bgp-agent.