diff --git a/neutron/api/rpc/handlers/dvr_rpc.py b/neutron/api/rpc/handlers/dvr_rpc.py index 2e2248039d4..c54f592110f 100644 --- a/neutron/api/rpc/handlers/dvr_rpc.py +++ b/neutron/api/rpc/handlers/dvr_rpc.py @@ -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. diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py index b398801aa1d..80b35e795d7 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py @@ -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) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py index fa6c3a81a31..4cf35fd3431 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py @@ -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): diff --git a/releasenotes/notes/add-dvr-src-mac-flow-for-allowed-address-pairs-acd6d700cd7dd560.yaml b/releasenotes/notes/add-dvr-src-mac-flow-for-allowed-address-pairs-acd6d700cd7dd560.yaml new file mode 100644 index 00000000000..c2fe0c6773a --- /dev/null +++ b/releasenotes/notes/add-dvr-src-mac-flow-for-allowed-address-pairs-acd6d700cd7dd560.yaml @@ -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 `_.