Install DVR src to MAC flow for AAPs

Add missing flows in distributed virtual router for MAC address
specified in allowed address pairs.

Closes-Bug: #2093248

Change-Id: Ife280712d6f45704a96a77ec3bfc07daa2e8e229
This commit is contained in:
Vasyl Saienko
2025-01-07 16:28:55 +02:00
committed by Brian Haley
parent da06d1a7d8
commit 1364715541
4 changed files with 164 additions and 10 deletions

View File

@@ -65,6 +65,13 @@ class DVRServerRpcApi:
return cctxt.call(
context, 'get_subnet_for_dvr', subnet=subnet, fixed_ips=fixed_ips)
@log_helpers.log_method_call
def get_ports(self, context, filters):
# NOTE(mtomaska): The MetadataRpcCallback (server side) API version 1.0
# exposes get_ports, under the PLUGIN topic and None namespace.
cctxt = self.client.prepare()
return cctxt.call(context, 'get_ports', filters=filters)
class DVRServerRpcCallback:
"""Plugin-side RPC (implementation) for agent-to-plugin interaction.

View File

@@ -28,6 +28,7 @@ from osprofiler import profiler
from neutron.agent.common import ovs_lib
from neutron.agent.linux.openvswitch_firewall import firewall as ovs_firewall
from neutron.common import utils as n_utils
from neutron.ipam import utils as ipam_utils
LOG = logging.getLogger(__name__)
@@ -530,6 +531,12 @@ class OVSDVRNeutronAgent:
def _bind_port_on_dvr_subnet(self, port, lvm, fixed_ips,
device_owner):
ports = self.plugin_rpc.get_ports(self.context,
filters={'id': [port.vif_id]})
aaps = []
if len(ports) == 1:
aaps = ports[0].get("allowed_address_pairs", [])
# Handle new compute port added use-case
subnet_uuid = None
for ips in fixed_ips:
@@ -566,12 +573,24 @@ class OVSDVRNeutronAgent:
if lvm.network_type in ovs_constants.DVR_PHYSICAL_NETWORK_TYPES:
vlan_to_use = lvm.segmentation_id
# create a rule for this vm port
dst_port = ovsport.get_ofport()
self.int_br.install_dvr_to_src_mac(
network_type=lvm.network_type,
vlan_tag=vlan_to_use,
gateway_mac=subnet_info['gateway_mac'],
dst_mac=ovsport.get_mac(),
dst_port=ovsport.get_ofport())
dst_port=dst_port)
for aap in aaps:
aap_ip_cidr = netaddr.IPNetwork(aap['ip_address'])
if n_utils.is_cidr_host(str(aap_ip_cidr.cidr)):
if ipam_utils.check_subnet_ip(
ldm.subnet['cidr'], str(aap_ip_cidr.ip)):
self.int_br.install_dvr_to_src_mac(
network_type=lvm.network_type,
vlan_tag=vlan_to_use,
gateway_mac=subnet_info['gateway_mac'],
dst_mac=aap["mac_address"],
dst_port=dst_port)
def _bind_centralized_snat_port_on_dvr_subnet(self, port, lvm,
fixed_ips, device_owner):
@@ -765,6 +784,12 @@ class OVSDVRNeutronAgent:
self.local_ports.pop(port.vif_id, None)
def _unbind_port_on_dvr_subnet(self, port, lvm):
ports = self.plugin_rpc.get_ports(self.context,
filters={'id': [port.vif_id]})
aaps = []
if len(ports) == 1:
aaps = ports[0].get("allowed_address_pairs", [])
ovsport = self.local_ports[port.vif_id]
# This confirms that this compute port being removed belonged
# to a dvr hosted subnet.
@@ -774,6 +799,16 @@ class OVSDVRNeutronAgent:
for sub_uuid in subnet_ids:
if sub_uuid not in self.local_dvr_map:
continue
if aaps:
local_compute_ports = (
self.plugin_rpc.get_ports_on_host_by_subnet(
self.context, self.host, sub_uuid))
local_aap_macs = set()
for lport in local_compute_ports:
if lport.id != port.vif_id:
local_aap_macs.update({
aap["mac_address"] for aap in lport.get(
"allowed_address_pairs", [])})
ldm = self.local_dvr_map[sub_uuid]
ldm.remove_compute_ofport(port.vif_id)
vlan_to_use = lvm.vlan
@@ -783,6 +818,15 @@ class OVSDVRNeutronAgent:
self.int_br.delete_dvr_to_src_mac(
network_type=lvm.network_type,
vlan_tag=vlan_to_use, dst_mac=ovsport.get_mac())
for aap in aaps:
aap_ip_cidr = netaddr.IPNetwork(aap['ip_address'])
if n_utils.is_cidr_host(str(aap_ip_cidr.cidr)):
if ipam_utils.check_subnet_ip(ldm.subnet['cidr'],
str(aap_ip_cidr.ip)):
if aap["mac_address"] not in local_aap_macs:
self.int_br.delete_dvr_to_src_mac(
network_type=lvm.network_type,
vlan_tag=vlan_to_use, dst_mac=aap["mac_address"])
# release port state
self.local_ports.pop(port.vif_id, None)

View File

@@ -3403,6 +3403,9 @@ class TestOvsDvrNeutronAgent:
mock.patch.object(self.agent.dvr_agent.plugin_rpc,
'get_ports_on_host_by_subnet',
return_value=[]),\
mock.patch.object(self.agent.dvr_agent.plugin_rpc,
'get_ports',
return_value=[]),\
mock.patch.object(self.agent.dvr_agent.int_br,
'get_vif_port_by_id',
return_value=self._port),\
@@ -3474,14 +3477,36 @@ class TestOvsDvrNeutronAgent:
phys_br.assert_not_called()
def _test_port_bound_for_dvr_on_vxlan_network(
self, device_owner, ip_version=n_const.IP_VERSION_4):
self, device_owner, ip_version=n_const.IP_VERSION_4, aaps=False):
self._setup_for_dvr_test()
port_obj = {"id": "fake-port-uuid"}
aap_mac = 'aa:bb:cc:dd:ee:ff'
aap_mac2 = 'aa:bb:cc:dd:ee:fe'
aap_mac3 = 'aa:bb:cc:dd:ee:fd'
if ip_version == n_const.IP_VERSION_4:
gateway_ip = '1.1.1.1'
cidr = '1.1.1.0/24'
if aaps:
port_obj["allowed_address_pairs"] = [
{'ip_address': '1.1.1.10/32',
'mac_address': aap_mac},
{'ip_address': '1.1.1.11',
'mac_address': aap_mac2},
{'ip_address': '0.0.0.0/0',
'mac_address': aap_mac3}
]
else:
gateway_ip = '2001:100::1'
cidr = '2001:100::0/64'
if aaps:
port_obj["allowed_address_pairs"] = [
{'ip_address': '2001:100::10/128',
'mac_address': aap_mac},
{'ip_address': '2001:100::11',
'mac_address': aap_mac2},
{'ip_address': '2001:100::0/64',
'mac_address': aap_mac3},
]
network_type = n_const.TYPE_VXLAN
self._port.vif_mac = gateway_mac = 'aa:bb:cc:11:22:33'
self._port.dvr_mac = self.agent.dvr_agent.dvr_mac_address
@@ -3503,6 +3528,9 @@ class TestOvsDvrNeutronAgent:
mock.patch.object(self.agent.dvr_agent.plugin_rpc,
'get_ports_on_host_by_subnet',
return_value=[]),\
mock.patch.object(self.agent.dvr_agent.plugin_rpc,
'get_ports',
return_value=[port_obj]),\
mock.patch.object(self.agent.dvr_agent.int_br,
'get_vif_port_by_id',
return_value=self._port),\
@@ -3549,6 +3577,7 @@ class TestOvsDvrNeutronAgent:
segmentation_id,
self._compute_fixed_ips,
device_owner, False)
expected_on_int_br = [
mock.call.install_dvr_to_src_mac(
network_type=network_type,
@@ -3556,9 +3585,24 @@ class TestOvsDvrNeutronAgent:
dst_mac=self._compute_port.vif_mac,
dst_port=self._compute_port.ofport,
vlan_tag=lvid,
),
] + self._expected_port_bound(self._compute_port, lvid, False,
network_type)
)]
if aaps:
expected_on_int_br += [
mock.call.install_dvr_to_src_mac(
network_type=network_type,
gateway_mac=gateway_mac,
dst_mac=aap_mac,
dst_port=self._compute_port.ofport,
vlan_tag=lvid),
mock.call.install_dvr_to_src_mac(
network_type=network_type,
gateway_mac=gateway_mac,
dst_mac=aap_mac2,
dst_port=self._compute_port.ofport,
vlan_tag=lvid),
]
expected_on_int_br += self._expected_port_bound(
self._compute_port, lvid, False, network_type)
int_br.assert_has_calls(expected_on_int_br)
tun_br.assert_not_called()
phys_br.assert_not_called()
@@ -3583,6 +3627,11 @@ class TestOvsDvrNeutronAgent:
self._test_port_bound_for_dvr_on_vxlan_network(
device_owner=DEVICE_OWNER_COMPUTE,
ip_version=n_const.IP_VERSION_6)
self._test_port_bound_for_dvr_on_vxlan_network(
device_owner=DEVICE_OWNER_COMPUTE, aaps=True)
self._test_port_bound_for_dvr_on_vxlan_network(
device_owner=DEVICE_OWNER_COMPUTE,
ip_version=n_const.IP_VERSION_6, aaps=True)
def test_port_bound_for_dvr_with_dhcp_ports(self):
self._test_port_bound_for_dvr_on_physical_network(
@@ -3780,6 +3829,9 @@ class TestOvsDvrNeutronAgent:
mock.patch.object(self.agent.dvr_agent.plugin_rpc,
'get_ports_on_host_by_subnet',
return_value=[]),\
mock.patch.object(self.agent.dvr_agent.plugin_rpc,
'get_ports',
return_value=[]),\
mock.patch.object(self.agent.dvr_agent.int_br,
'get_vif_port_by_id',
return_value=self._port),\
@@ -3928,6 +3980,9 @@ class TestOvsDvrNeutronAgent:
mock.patch.object(self.agent.dvr_agent.plugin_rpc,
'get_ports_on_host_by_subnet',
return_value=[]),\
mock.patch.object(self.agent.dvr_agent.plugin_rpc,
'get_ports',
return_value=[]),\
mock.patch.object(self.agent, 'int_br', new=int_br),\
mock.patch.object(self.agent, 'tun_br', new=tun_br),\
mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),\
@@ -4024,15 +4079,37 @@ class TestOvsDvrNeutronAgent:
tun_br.assert_has_calls(expected)
phys_br.assert_not_called()
def _test_treat_devices_removed_for_dvr(self, device_owner,
ip_version=n_const.IP_VERSION_4):
def _test_treat_devices_removed_for_dvr(
self, device_owner, ip_version=n_const.IP_VERSION_4, aaps=False):
self._setup_for_dvr_test()
port_obj = {"id": "fake-port-uuid"}
aap_mac = 'aa:bb:cc:dd:ee:ff'
aap_mac2 = 'aa:bb:cc:dd:ee:fe'
aap_mac3 = 'aa:bb:cc:dd:ee:fd'
if ip_version == n_const.IP_VERSION_4:
gateway_ip = '1.1.1.1'
cidr = '1.1.1.0/24'
if aaps:
port_obj["allowed_address_pairs"] = [
{'ip_address': '1.1.1.10/32',
'mac_address': aap_mac},
{'ip_address': '1.1.1.11',
'mac_address': aap_mac2},
{'ip_address': '0.0.0.0/0',
'mac_address': aap_mac3}
]
else:
gateway_ip = '2001:100::1'
cidr = '2001:100::0/64'
if aaps:
port_obj["allowed_address_pairs"] = [
{'ip_address': '2001:100::10/128',
'mac_address': aap_mac},
{'ip_address': '2001:100::11',
'mac_address': aap_mac2},
{'ip_address': '2001:100::0/0',
'mac_address': aap_mac3}
]
self._port.dvr_mac = self.agent.dvr_agent.dvr_mac_address
gateway_mac = 'aa:bb:cc:11:22:33'
int_br = mock.create_autospec(self.agent.int_br)
@@ -4051,6 +4128,9 @@ class TestOvsDvrNeutronAgent:
mock.patch.object(self.agent.dvr_agent.int_br,
'get_vif_port_by_id',
return_value=self._port),\
mock.patch.object(self.agent.dvr_agent.plugin_rpc,
'get_ports',
return_value=[]),\
mock.patch.object(self.agent, 'int_br', new=int_br),\
mock.patch.object(self.agent, 'tun_br', new=tun_br),\
mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),\
@@ -4105,6 +4185,9 @@ class TestOvsDvrNeutronAgent:
self._compute_port.vif_id],
'failed_devices_up': [],
'failed_devices_down': []}),\
mock.patch.object(self.agent.dvr_agent.plugin_rpc,
'get_ports',
return_value=[port_obj]),\
mock.patch.object(self.agent, 'int_br', new=int_br),\
mock.patch.object(self.agent, 'tun_br', new=tun_br),\
mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),\
@@ -4112,13 +4195,27 @@ class TestOvsDvrNeutronAgent:
failed_devices = {'added': set(), 'removed': set()}
failed_devices['removed'] = self.agent.treat_devices_removed(
[self._compute_port.vif_id])
int_br.assert_has_calls([
expected_delete_dvr_src_mac = [
mock.call.delete_dvr_to_src_mac(
network_type='vxlan',
vlan_tag=lvid,
dst_mac=self._compute_port.vif_mac,
),
])
)
]
if aaps:
expected_delete_dvr_src_mac += [
mock.call.delete_dvr_to_src_mac(
network_type='vxlan',
vlan_tag=lvid,
dst_mac=aap_mac,
),
mock.call.delete_dvr_to_src_mac(
network_type='vxlan',
vlan_tag=lvid,
dst_mac=aap_mac2,
)
]
int_br.assert_has_calls(expected_delete_dvr_src_mac)
tun_br.assert_not_called()
def test_treat_devices_removed_for_dvr_with_compute_ports(self):

View File

@@ -0,0 +1,6 @@
---
fixes:
- |
Fix connection issue to allowed address pair address that is located
behind a distributed virtual router by adding a missing flow.
For more information, see bug `2093248 <https://launchpad.net/bugs/2093248>`_.