diff --git a/neutron/agent/l3/dvr_edge_router.py b/neutron/agent/l3/dvr_edge_router.py index 514a83a8699..6287a283f9e 100644 --- a/neutron/agent/l3/dvr_edge_router.py +++ b/neutron/agent/l3/dvr_edge_router.py @@ -21,6 +21,7 @@ from neutron.agent.l3 import dvr_snat_ns from neutron.agent.l3 import router_info as router from neutron.agent.linux import ip_lib from neutron.agent.linux import iptables_manager +from neutron.common import constants as n_const LOG = logging.getLogger(__name__) @@ -260,3 +261,92 @@ class DvrEdgeRouter(dvr_local_router.DvrLocalRouter): bridge=self.agent_conf.external_network_bridge, namespace=self.snat_namespace.name, prefix=router.EXTERNAL_DEV_PREFIX) + + def get_snat_external_device_interface_name(self, ex_gw_port): + long_name = router.EXTERNAL_DEV_PREFIX + ex_gw_port['id'] + return long_name[:self.driver.DEV_NAME_LEN] + + def _get_centralized_fip_cidr_set(self): + """Returns the fip_cidr set for centralized floatingips.""" + interface_name = self.get_snat_external_device_interface_name( + self.get_ex_gw_port()) + device = ip_lib.IPDevice( + interface_name, namespace=self.snat_namespace.name) + return set([addr['cidr'] for addr in device.addr.list()]) + + def get_router_cidrs(self, device): + """Over-ride the get_router_cidrs function to return the list. + + This function is overridden to provide the complete list of + floating_ip cidrs that the router hosts. + This includes the centralized floatingip cidr list and the + regular floatingip cidr list that are bound to fip namespace. + """ + fip_cidrs = super(DvrEdgeRouter, self).get_router_cidrs(device) + centralized_cidrs = set() + if self.get_ex_gw_port(): + centralized_cidrs = self._get_centralized_fip_cidr_set() + return fip_cidrs | centralized_cidrs + + def remove_centralized_floatingip(self, fip_cidr): + """Function to handle the centralized Floatingip remove.""" + if not self.get_ex_gw_port(): + return + if not self._is_this_snat_host(): + return + interface_name = self.get_snat_external_device_interface_name( + self.get_ex_gw_port()) + device = ip_lib.IPDevice( + interface_name, namespace=self.snat_namespace.name) + device.delete_addr_and_conntrack_state(fip_cidr) + self.process_floating_ip_nat_rules_for_centralized_floatingip() + + def add_centralized_floatingip(self, fip, fip_cidr): + """Function to handle the centralized Floatingip addition.""" + if not self.get_ex_gw_port(): + return + if not self._is_this_snat_host(): + return + interface_name = self.get_snat_external_device_interface_name( + self.get_ex_gw_port()) + device = ip_lib.IPDevice( + interface_name, namespace=self.snat_namespace.name) + try: + device.addr.add(fip_cidr) + except RuntimeError: + LOG.warning("Unable to configure IP address for centralized " + "floating IP: %s", fip['id']) + return lib_constants.FLOATINGIP_STATUS_ERROR + self.process_floating_ip_nat_rules_for_centralized_floatingip() + # Send a GARP message on the external interface for the + # centralized floatingip configured. + ip_lib.send_ip_addr_adv_notif(self.snat_namespace.name, + interface_name, + fip['floating_ip_address'], + self.agent_conf) + return lib_constants.FLOATINGIP_STATUS_ACTIVE + + def _centralized_floating_forward_rules(self, floating_ip, fixed_ip): + return [('PREROUTING', '-d %s/32 -j DNAT --to-destination %s' % + (floating_ip, fixed_ip)), + ('OUTPUT', '-d %s/32 -j DNAT --to-destination %s' % + (floating_ip, fixed_ip)), + ('float-snat', '-s %s/32 -j SNAT --to-source %s' % + (fixed_ip, floating_ip))] + + def _set_floating_ip_nat_rules_for_centralized_floatingip(self, fip): + if fip.get(n_const.DVR_SNAT_BOUND): + fixed = fip['fixed_ip_address'] + fip_ip = fip['floating_ip_address'] + for chain, rule in self._centralized_floating_forward_rules( + fip_ip, fixed): + self.snat_iptables_manager.ipv4['nat'].add_rule( + chain, rule, tag='floating_ip') + + def process_floating_ip_nat_rules_for_centralized_floatingip(self): + self.snat_iptables_manager.ipv4['nat'].clear_rules_by_tag( + 'floating_ip') + floating_ips = self.get_floating_ips() + for fip in floating_ips: + self._set_floating_ip_nat_rules_for_centralized_floatingip(fip) + self.snat_iptables_manager.apply() diff --git a/neutron/agent/l3/dvr_local_router.py b/neutron/agent/l3/dvr_local_router.py index 2d2ec0d7863..4137fc84280 100644 --- a/neutron/agent/l3/dvr_local_router.py +++ b/neutron/agent/l3/dvr_local_router.py @@ -43,24 +43,23 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase): super(DvrLocalRouter, self).__init__(host, *args, **kwargs) self.floating_ips_dict = {} + self.centralized_floatingips_set = set() # Linklocal subnet for router and floating IP namespace link self.rtr_fip_subnet = None self.rtr_fip_connect = False self.fip_ns = None self._pending_arp_set = set() - def get_floating_ips(self): - """Filter Floating IPs to be hosted on this agent.""" - floating_ips = super(DvrLocalRouter, self).get_floating_ips() - return [i for i in floating_ips if ( - (i['host'] == self.host) or - (i.get('dest_host') == self.host))] - - def floating_forward_rules(self, floating_ip, fixed_ip): + def floating_forward_rules(self, fip): """Override this function defined in router_info for dvr routers.""" if not self.fip_ns: return [] + if fip.get(n_const.DVR_SNAT_BOUND): + return [] + + fixed_ip = fip['fixed_ip_address'] + floating_ip = fip['floating_ip_address'] rtr_2_fip_name = self.fip_ns.get_rtr_ext_device_name(self.router_id) dnat_from_floatingip_to_fixedip = ( 'PREROUTING', '-d %s/32 -i %s -j DNAT --to-destination %s' % ( @@ -83,8 +82,25 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase): 'FORWARD', '-s %s/32 -j $float-snat' % fixed_ip) return [mark_traffic_to_floating_ip, mark_traffic_from_fixed_ip] + def add_centralized_floatingip(self, fip, fip_cidr): + """Implements floatingip in centralized network node. + This is a dummy function and is overridden in dvr_edge_router.py + to add the floatingip function to the snat namespace. + """ + + def remove_centralized_floatingip(self, fip_cidr): + """Removes floatingip from centralized network node. + This is a dummy function and is overridden in dvr_edge_router.py + to remove the floatingip function from the snat namespace. + """ + def floating_ip_added_dist(self, fip, fip_cidr): """Add floating IP to FIP namespace.""" + if fip.get(n_const.DVR_SNAT_BOUND): + floating_ip_status = self.add_centralized_floatingip(fip, fip_cidr) + if floating_ip_status == lib_constants.FLOATINGIP_STATUS_ACTIVE: + self.centralized_floatingips_set.add(fip_cidr) + return floating_ip_status floating_ip = fip['floating_ip_address'] fixed_ip = fip['fixed_ip_address'] self._add_floating_ip_rule(floating_ip, fixed_ip) @@ -103,6 +119,7 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase): ip_lib.send_ip_addr_adv_notif(fip_ns_name, interface_name, floating_ip) + return lib_constants.FLOATINGIP_STATUS_ACTIVE def _add_floating_ip_rule(self, floating_ip, fixed_ip): rule_pr = self.fip_ns.allocate_rule_priority(floating_ip) @@ -124,6 +141,10 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase): def floating_ip_removed_dist(self, fip_cidr): """Remove floating IP from FIP namespace.""" + if fip_cidr in self.centralized_floatingips_set: + self.remove_centralized_floatingip(fip_cidr) + self.centralized_floatingips_set.remove(fip_cidr) + return floating_ip = fip_cidr.split('/')[0] fip_2_rtr_name = self.fip_ns.get_int_device_name(self.router_id) if self.rtr_fip_subnet is None: @@ -147,8 +168,7 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase): def add_floating_ip(self, fip, interface_name, device): # Special Handling for DVR - update FIP namespace ip_cidr = common_utils.ip_to_cidr(fip['floating_ip_address']) - self.floating_ip_added_dist(fip, ip_cidr) - return lib_constants.FLOATINGIP_STATUS_ACTIVE + return self.floating_ip_added_dist(fip, ip_cidr) def remove_floating_ip(self, device, ip_cidr): self.floating_ip_removed_dist(ip_cidr) @@ -417,7 +437,13 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase): return next( (p for p in fip_ports if p['network_id'] == ext_net_id), None) + def get_snat_external_device_interface_name(self, port_id): + pass + def get_external_device_interface_name(self, ex_gw_port): + floating_ips = self.get_floating_ips() + if not self._get_floatingips_bound_to_host(floating_ips): + return self.get_snat_external_device_interface_name(ex_gw_port) fip_int = self.fip_ns.get_int_device_name(self.router_id) if ip_lib.device_exists(fip_int, namespace=self.fip_ns.get_name()): return self.fip_ns.get_rtr_ext_device_name(self.router_id) @@ -519,6 +545,12 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase): ext_scope_mark) return ports_scopemark + def _get_floatingips_bound_to_host(self, floating_ips): + """Filter Floating IPs to be hosted on this agent.""" + return [i for i in floating_ips + if (i['host'] == self.host or + i.get('dest_host') == self.host)] + def process_external(self): ex_gw_port = self.get_ex_gw_port() if ex_gw_port: diff --git a/neutron/agent/l3/router_info.py b/neutron/agent/l3/router_info.py index 8deef250440..14727dfb012 100644 --- a/neutron/agent/l3/router_info.py +++ b/neutron/agent/l3/router_info.py @@ -161,7 +161,9 @@ class RouterInfo(object): """Filter Floating IPs to be hosted on this agent.""" return self.router.get(lib_constants.FLOATINGIP_KEY, []) - def floating_forward_rules(self, floating_ip, fixed_ip): + def floating_forward_rules(self, fip): + fixed_ip = fip['fixed_ip_address'] + floating_ip = fip['floating_ip_address'] return [('PREROUTING', '-d %s/32 -j DNAT --to-destination %s' % (floating_ip, fixed_ip)), ('OUTPUT', '-d %s/32 -j DNAT --to-destination %s' % @@ -215,9 +217,7 @@ class RouterInfo(object): # Loop once to ensure that floating ips are configured. for fip in floating_ips: # Rebuild iptables rules for the floating ip. - fixed = fip['fixed_ip_address'] - fip_ip = fip['floating_ip_address'] - for chain, rule in self.floating_forward_rules(fip_ip, fixed): + for chain, rule in self.floating_forward_rules(fip): self.iptables_manager.ipv4['nat'].add_rule(chain, rule, tag='floating_ip') diff --git a/neutron/tests/functional/agent/l3/test_dvr_router.py b/neutron/tests/functional/agent/l3/test_dvr_router.py index 9d024e25c9b..ef31193141c 100644 --- a/neutron/tests/functional/agent/l3/test_dvr_router.py +++ b/neutron/tests/functional/agent/l3/test_dvr_router.py @@ -445,6 +445,7 @@ class TestDvrRouter(framework.L3AgentTestFramework): enable_ha=False, enable_snat=False, enable_gw=True, + snat_bound_fip=False, agent=None, extra_routes=False, enable_floating_ip=True, @@ -465,6 +466,9 @@ class TestDvrRouter(framework.L3AgentTestFramework): if enable_floating_ip: floating_ip = router['_floatingips'][0] floating_ip['host'] = agent.conf.host + + if snat_bound_fip: + floating_ip[n_const.DVR_SNAT_BOUND] = True if enable_gw: external_gw_port = router['gw_port'] router['gw_port'][portbindings.HOST_ID] = agent.conf.host @@ -481,6 +485,7 @@ class TestDvrRouter(framework.L3AgentTestFramework): floating_ip['port_id'] = internal_ports[0]['id'] floating_ip['status'] = 'ACTIVE' + if not snat_bound_fip: self._add_fip_agent_gw_port_info_to_router(router, external_gw_port) return router @@ -683,10 +688,7 @@ class TestDvrRouter(framework.L3AgentTestFramework): # In the router namespace, check the iptables rules are set correctly for fip in floating_ips: - floatingip = fip['floating_ip_address'] - fixedip = fip['fixed_ip_address'] - expected_rules = router.floating_forward_rules(floatingip, - fixedip) + expected_rules = router.floating_forward_rules(fip) self._assert_iptables_rules_exist( router.iptables_manager, 'nat', expected_rules) @@ -986,6 +988,19 @@ class TestDvrRouter(framework.L3AgentTestFramework): internal_dev_name, namespace=snat_ns) return qg_device_created_successfully, sg_device_created_successfully + def test_snat_bound_floating_ip(self): + """Test to validate the snat bound floatingip lifecycle.""" + self.agent.conf.agent_mode = lib_constants.L3_AGENT_MODE_DVR_SNAT + router_info = self.generate_dvr_router_info(snat_bound_fip=True) + router1 = self.manage_router(self.agent, router_info) + snat_bound_floatingips = router_info[lib_constants.FLOATINGIP_KEY] + self._assert_snat_namespace_exists(router1) + # In the snat namespace, check the iptables rules are set correctly + for fip in snat_bound_floatingips: + expected_rules = router1.floating_forward_rules(fip) + self._assert_iptables_rules_exist( + router1.snat_iptables_manager, 'nat', expected_rules) + def test_dvr_router_snat_namespace_with_interface_remove(self): """Test to validate the snat namespace with interface remove. diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index 2b46f4fe70a..2bdaed6148e 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -1182,6 +1182,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): fip_gw_port.return_value = agent_gateway_port[0] ri.create_dvr_external_gateway_on_agent(ext_gw_port) ri.connect_rtr_2_fip() + ri._get_floatingips_bound_to_host = mock.Mock( + return_value=True) self.assertTrue(fip_gw_port.called) self.assertTrue(create_fip.called) self.assertEqual(agent_gateway_port[0], @@ -1200,6 +1202,72 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): create_fip.assert_called_once_with() self.assertEqual(1, ri.fip_ns.create_rtr_2_fip_link.call_count) + @mock.patch.object(lla.LinkLocalAllocator, '_write') + def test_floating_ip_centralized(self, lla_write): + fake_network_id = _uuid() + subnet_id = _uuid() + fake_floatingips = {'floatingips': [ + {'id': _uuid(), + 'floating_ip_address': '20.0.0.3', + 'fixed_ip_address': '192.168.0.1', + 'floating_network_id': _uuid(), + 'port_id': _uuid(), + 'dvr_snat_bound': True, + 'host': None}]} + agent_gateway_port = ( + [{'fixed_ips': [ + {'ip_address': '20.0.0.30', + 'prefixlen': 24, + 'subnet_id': subnet_id}], + 'subnets': [ + {'id': subnet_id, + 'cidr': '20.0.0.0/24', + 'gateway_ip': '20.0.0.1'}], + 'id': _uuid(), + 'network_id': fake_network_id, + 'mac_address': 'ca:fe:de:ad:be:ef'}] + ) + + router = l3_test_common.prepare_router_data(enable_snat=True) + router[lib_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips'] + router[n_const.FLOATINGIP_AGENT_INTF_KEY] = agent_gateway_port + router['distributed'] = True + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + self._set_ri_kwargs(agent, router['id'], router) + ri = dvr_router.DvrEdgeRouter(HOSTNAME, **self.ri_kwargs) + ext_gw_port = ri.router.get('gw_port') + ri.fip_ns = agent.get_fip_ns(ext_gw_port['network_id']) + agent.process_router_add = mock.Mock() + ri.fip_ns.create_rtr_2_fip_link = mock.Mock() + with mock.patch.object(ri, 'get_floating_ips') as fips, \ + mock.patch.object(ri, + 'add_centralized_floatingip') as add_fip, \ + mock.patch.object(ri, 'get_floating_agent_gw_interface' + ) as fip_gw_port, \ + mock.patch.object(ri.fip_ns, + 'create') as create_fip, \ + mock.patch.object(ri, + 'remove_centralized_floatingip') as rem_fip: + fips.return_value = fake_floatingips + fip_gw_port.return_value = agent_gateway_port[0] + add_fip.return_value = lib_constants.FLOATINGIP_STATUS_ACTIVE + ri.create_dvr_external_gateway_on_agent(ext_gw_port) + ri.connect_rtr_2_fip() + self.assertTrue(fip_gw_port.called) + self.assertTrue(create_fip.called) + self.assertEqual(agent_gateway_port[0], + ri.fip_ns.agent_gateway_port) + self.assertTrue(ri.rtr_fip_connect) + # Now let us associate the fip to the router + status = ri.floating_ip_added_dist(fips, "192.168.0.1/32") + add_fip.assert_called_once_with(fips, "192.168.0.1/32") + self.assertEqual(lib_constants.FLOATINGIP_STATUS_ACTIVE, status) + self.assertEqual(set(["192.168.0.1/32"]), + ri.centralized_floatingips_set) + ri.floating_ip_removed_dist("192.168.0.1/32") + rem_fip.assert_called_once_with("192.168.0.1/32") + self.assertEqual(set([]), ri.centralized_floatingips_set) + @mock.patch.object(lla.LinkLocalAllocator, '_write') def test_create_dvr_fip_interfaces_for_late_binding(self, lla_write): fake_network_id = _uuid() @@ -1291,6 +1359,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): fip_gw_port.return_value = agent_gateway_port[0] ri.create_dvr_external_gateway_on_agent(ext_gw_port) ri.connect_rtr_2_fip() + ri._get_floatingips_bound_to_host = mock.Mock( + return_value=True) self.assertTrue(fip_gw_port.called) self.assertEqual(agent_gateway_port[0], ri.fip_ns.agent_gateway_port) @@ -1339,6 +1409,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): fip_gw_port.return_value = agent_gateway_port[0] ri.create_dvr_external_gateway_on_agent(ext_gw_port) ri.connect_rtr_2_fip() + ri._get_floatingips_bound_to_host = mock.Mock( + return_value=True) self.assertTrue(fip_gw_port.called) self.assertEqual(agent_gateway_port[0], ri.fip_ns.agent_gateway_port) diff --git a/neutron/tests/unit/agent/l3/test_dvr_local_router.py b/neutron/tests/unit/agent/l3/test_dvr_local_router.py index 3599e76857c..02605d56906 100644 --- a/neutron/tests/unit/agent/l3/test_dvr_local_router.py +++ b/neutron/tests/unit/agent/l3/test_dvr_local_router.py @@ -21,6 +21,7 @@ from oslo_log import log from oslo_utils import uuidutils from neutron.agent.l3 import agent as l3_agent +from neutron.agent.l3 import dvr_edge_router as dvr_edge_rtr from neutron.agent.l3 import dvr_local_router as dvr_router from neutron.agent.l3 import link_local_allocator as lla from neutron.agent.l3 import router_info @@ -178,6 +179,7 @@ class TestDvrRouterOperations(base.BaseTestCase): ri.rtr_fip_connect = True ex_gw_port = {'network_id': 'fake_net_id'} ri.create_dvr_external_gateway_on_agent(ex_gw_port) + ri._get_floatingips_bound_to_host = mock.Mock(return_value=True) ri.fip_ns.create_or_update_gateway_port.assert_called_once_with( fip_agent_port) @@ -231,16 +233,16 @@ class TestDvrRouterOperations(base.BaseTestCase): fips = ri.get_floating_ips() - self.assertEqual([{'host': HOSTNAME}], fips) + self.assertEqual( + [{'host': HOSTNAME}, {'host': mock.sentinel.otherhost}], fips) def test_floating_forward_rules_no_fip_ns(self): router = mock.MagicMock() router.get.return_value = [{'host': HOSTNAME}, {'host': mock.sentinel.otherhost}] + fip = {'id': _uuid()} ri = self._create_router(router) - floating_ip = mock.Mock() - fixed_ip = mock.Mock() - self.assertFalse(ri.floating_forward_rules(floating_ip, fixed_ip)) + self.assertFalse(ri.floating_forward_rules(fip)) def test_floating_forward_rules(self): router = mock.MagicMock() @@ -250,6 +252,9 @@ class TestDvrRouterOperations(base.BaseTestCase): floating_ip = '15.1.2.3' rtr_2_fip_name = 'fake_router' fixed_ip = '192.168.0.1' + fip = {'id': _uuid(), + 'fixed_ip_address': '192.168.0.1', + 'floating_ip_address': '15.1.2.3'} instance = mock.Mock() instance.get_rtr_ext_device_name = mock.Mock( return_value=rtr_2_fip_name) @@ -260,7 +265,7 @@ class TestDvrRouterOperations(base.BaseTestCase): snat_from_fixedip_to_floatingip = ( 'float-snat', '-s %s/32 -j SNAT --to-source %s' % ( fixed_ip, floating_ip)) - actual = ri.floating_forward_rules(floating_ip, fixed_ip) + actual = ri.floating_forward_rules(fip) expected = [dnat_from_floatingip_to_fixedip, snat_from_fixedip_to_floatingip] self.assertEqual(expected, actual) @@ -405,9 +410,13 @@ class TestDvrRouterOperations(base.BaseTestCase): table=16, priority=FIP_PRI) - def _test_add_floating_ip(self, ri, fip, is_failure): - ri.floating_ip_added_dist = mock.Mock() - + def _test_add_floating_ip(self, ri, fip, is_failure=False): + if not is_failure: + ri.floating_ip_added_dist = mock.Mock( + return_value=lib_constants.FLOATINGIP_STATUS_ACTIVE) + else: + ri.floating_ip_added_dist = mock.Mock( + return_value=lib_constants.FLOATINGIP_STATUS_ERROR) result = ri.add_floating_ip(fip, mock.sentinel.interface_name, mock.sentinel.device) @@ -419,10 +428,18 @@ class TestDvrRouterOperations(base.BaseTestCase): ri = self._create_router(mock.MagicMock()) ip = '15.1.2.3' fip = {'floating_ip_address': ip} - result = self._test_add_floating_ip(ri, fip, True) + result = self._test_add_floating_ip(ri, fip) ri.floating_ip_added_dist.assert_called_once_with(fip, ip + '/32') self.assertEqual(lib_constants.FLOATINGIP_STATUS_ACTIVE, result) + def test_add_floating_ip_failure(self): + ri = self._create_router(mock.MagicMock()) + ip = '15.1.2.3' + fip = {'floating_ip_address': ip} + result = self._test_add_floating_ip(ri, fip, True) + ri.floating_ip_added_dist.assert_called_once_with(fip, ip + '/32') + self.assertEqual(lib_constants.FLOATINGIP_STATUS_ERROR, result) + @mock.patch.object(router_info.RouterInfo, 'remove_floating_ip') def test_remove_floating_ip(self, super_remove_floating_ip): ri = self._create_router(mock.MagicMock()) @@ -694,14 +711,14 @@ class TestDvrRouterOperations(base.BaseTestCase): def test_external_gateway_removed_ext_gw_port_and_fip(self): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - agent.conf.agent_mode = 'dvr' + agent.conf.agent_mode = lib_constants.L3_AGENT_MODE_DVR_SNAT router = l3_test_common.prepare_router_data(num_internal_ports=2) router['gw_port_host'] = HOSTNAME self.mock_driver.unplug.reset_mock() external_net_id = router['gw_port']['network_id'] self._set_ri_kwargs(agent, router['id'], router) - ri = dvr_router.DvrLocalRouter(HOSTNAME, **self.ri_kwargs) + ri = dvr_edge_rtr.DvrEdgeRouter(HOSTNAME, **self.ri_kwargs) ri.remove_floating_ip = mock.Mock() agent._fetch_external_net_id = mock.Mock(return_value=external_net_id) ri.ex_gw_port = ri.router['gw_port'] diff --git a/releasenotes/notes/dvr_handle_unbound_floatingip_port-f12ae806b8be2065.yaml b/releasenotes/notes/dvr_handle_unbound_floatingip_port-f12ae806b8be2065.yaml new file mode 100644 index 00000000000..3e2bc4f9ca4 --- /dev/null +++ b/releasenotes/notes/dvr_handle_unbound_floatingip_port-f12ae806b8be2065.yaml @@ -0,0 +1,20 @@ +--- +features: + - | + Floating IPs associated with an unbound port with DVR routers will + not be distributed, but will be centralized and implemented in the + SNAT namespace of the Network node or ``dvr_snat`` node. + Floating IPs associated with allowed_address_pair port IP and are + bound to multiple active VMs with DVR routers will be implemented + in the SNAT namespace in the Network node or ``dvr_snat`` node. This + will address VRRP use cases. + More information about this is captured in + `bug 1583694 `__. +issues: + - | + While the bound port Floating IPs are distributed, the unbound port + Floating IPs are centralized. +fixes: + - | + Allows the unbound port Floating IPs to be configured + properly with DVR routers irrespective of its device_owner.