Merge "Add address scope to the OVN LSP port registers"
This commit is contained in:
commit
fc3868ca2d
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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',
|
||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user