Install centralized floating IP nat rules to all ha nodes

For L3 DVR HA router, the centralized floating IP nat rules are not
installed in every HA node snat namespace. So, install the rules to
all the router snat-namespace on every scheduled HA router host.

Closes-Bug: #1793527
Change-Id: I08132510b3ed374a3f85146498f3624a103873d7
This commit is contained in:
LIU Yulong 2018-09-20 21:30:09 +08:00
parent 7b6754e9d4
commit ee7660f593
7 changed files with 185 additions and 47 deletions

View File

@ -352,3 +352,11 @@ class DvrEdgeRouter(dvr_local_router.DvrLocalRouter):
for fip in floating_ips:
self._set_floating_ip_nat_rules_for_centralized_floatingip(fip)
self.snat_iptables_manager.apply()
def process_floating_ip_nat_rules(self):
if self._is_this_snat_host():
self.process_floating_ip_nat_rules_for_centralized_floatingip()
# Cover mixed dvr_snat and compute node, aka a dvr_snat node has both
# centralized and distributed floating IPs.
super(DvrEdgeRouter, self).process_floating_ip_nat_rules()

View File

@ -67,6 +67,12 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase):
if fip.get(lib_constants.DVR_SNAT_BOUND):
return []
# For dvr_no_external node should not process any floating IP
# iptables rules.
if (self.agent_conf.agent_mode ==
lib_constants.L3_AGENT_MODE_DVR_NO_EXTERNAL):
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)
@ -120,6 +126,12 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase):
# the floating IP is intended for this host should
# be done.
return
# dvr_no_external host should not process any floating IP route rules.
if (self.agent_conf.agent_mode ==
lib_constants.L3_AGENT_MODE_DVR_NO_EXTERNAL):
return
floating_ip = fip['floating_ip_address']
fixed_ip = fip['fixed_ip_address']
self._add_floating_ip_rule(floating_ip, fixed_ip)
@ -525,6 +537,29 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase):
def external_gateway_updated(self, ex_gw_port, interface_name):
pass
def process_floating_ip_nat_rules(self):
"""Configure NAT rules for the router's floating IPs.
Configures iptables rules for the floating ips of the given router
"""
# Clear out all iptables rules for floating ips
self.iptables_manager.ipv4['nat'].clear_rules_by_tag('floating_ip')
floating_ips = self.get_floating_ips()
# Loop once to ensure that floating ips are configured.
for fip in floating_ips:
# If floating IP is snat_bound, then the iptables rule should
# not be installed to qrouter namespace, since the mixed snat
# namespace may already install it.
if fip.get(lib_constants.DVR_SNAT_BOUND):
continue
# Rebuild iptables rules for the floating ip.
for chain, rule in self.floating_forward_rules(fip):
self.iptables_manager.ipv4['nat'].add_rule(
chain, rule, tag='floating_ip')
self.iptables_manager.apply()
def external_gateway_removed(self, ex_gw_port, interface_name):
# TODO(Carl) Should this be calling process_snat_dnat_for_fip?
self.process_floating_ip_nat_rules()

View File

@ -57,7 +57,9 @@ def prepare_router_data(ip_version=lib_constants.IP_VERSION_4,
enable_snat=None, num_internal_ports=1,
enable_floating_ip=False, enable_ha=False,
extra_routes=False, dual_stack=False, enable_gw=True,
v6_ext_gw_with_sub=True, **kwargs):
v6_ext_gw_with_sub=True,
snat_bound_fip=False,
**kwargs):
fixed_ips = []
subnets = []
gateway_mac = kwargs.get('gateway_mac', 'ca:fe:de:ad:be:ee')
@ -114,6 +116,7 @@ def prepare_router_data(ip_version=lib_constants.IP_VERSION_4,
'routes': routes,
'gw_port': ex_gw_port}
router_fips = router.get(lib_constants.FLOATINGIP_KEY, [])
if enable_floating_ip:
fip = {'id': _uuid(),
'port_id': _uuid(),
@ -123,7 +126,19 @@ def prepare_router_data(ip_version=lib_constants.IP_VERSION_4,
qos_policy_id = kwargs.get(qos_consts.QOS_POLICY_ID)
if qos_policy_id:
fip[qos_consts.QOS_POLICY_ID] = qos_policy_id
router[lib_constants.FLOATINGIP_KEY] = [fip]
router_fips.append(fip)
if snat_bound_fip:
fip = {'id': _uuid(),
'port_id': _uuid(),
'status': 'DOWN',
'floating_ip_address': '19.4.4.3',
'fixed_ip_address': '10.0.0.2'}
qos_policy_id = kwargs.get(qos_consts.QOS_POLICY_ID)
if qos_policy_id:
fip[qos_consts.QOS_POLICY_ID] = qos_policy_id
router_fips.append(fip)
router[lib_constants.FLOATINGIP_KEY] = router_fips
router_append_interface(router, count=num_internal_ports,
ip_version=ip_version, dual_stack=dual_stack)

View File

@ -478,7 +478,11 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
self.assertFalse(router.iptables_manager.is_chain_empty(
'nat', 'POSTROUTING'))
def _assert_floating_ip_chains(self, router):
def _assert_floating_ip_chains(self, router, snat_bound_fip=False):
if snat_bound_fip:
self.assertFalse(router.snat_iptables_manager.is_chain_empty(
'nat', 'float-snat'))
self.assertFalse(router.iptables_manager.is_chain_empty(
'nat', 'float-snat'))

View File

@ -71,6 +71,14 @@ class TestDvrRouter(framework.L3AgentTestFramework):
def test_dvr_router_lifecycle_ha_with_snat_with_fips(self):
self._dvr_router_lifecycle(enable_ha=True, enable_snat=True)
def test_dvr_lifecycle_no_ha_with_snat_with_fips_with_cent_fips(self):
self._dvr_router_lifecycle(enable_ha=False, enable_snat=True,
snat_bound_fip=True)
def test_dvr_lifecycle_ha_with_snat_with_fips_with_cent_fips(self):
self._dvr_router_lifecycle(enable_ha=True, enable_snat=True,
snat_bound_fip=True)
def _helper_create_dvr_router_fips_for_ext_network(
self, agent_mode, **dvr_router_kwargs):
self.agent.conf.agent_mode = agent_mode
@ -408,7 +416,8 @@ class TestDvrRouter(framework.L3AgentTestFramework):
def _dvr_router_lifecycle(self, enable_ha=False, enable_snat=False,
custom_mtu=2000,
ip_version=lib_constants.IP_VERSION_4,
dual_stack=False):
dual_stack=False,
snat_bound_fip=False):
'''Test dvr router lifecycle
:param enable_ha: sets the ha value for the router.
@ -423,7 +432,8 @@ class TestDvrRouter(framework.L3AgentTestFramework):
# We get the router info particular to a dvr router
router_info = self.generate_dvr_router_info(
enable_ha, enable_snat, extra_routes=True)
enable_ha, enable_snat, extra_routes=True,
snat_bound_fip=snat_bound_fip)
for key in ('_interfaces', '_snat_router_interfaces',
'_floatingip_agent_interfaces'):
for port in router_info[key]:
@ -480,9 +490,9 @@ class TestDvrRouter(framework.L3AgentTestFramework):
self._assert_internal_devices(router)
self._assert_dvr_external_device(router)
self._assert_dvr_gateway(router)
self._assert_dvr_floating_ips(router)
self._assert_dvr_floating_ips(router, snat_bound_fip=snat_bound_fip)
self._assert_snat_chains(router)
self._assert_floating_ip_chains(router)
self._assert_floating_ip_chains(router, snat_bound_fip=snat_bound_fip)
self._assert_metadata_chains(router)
self._assert_rfp_fpr_mtu(router, custom_mtu)
if enable_snat:
@ -527,21 +537,33 @@ class TestDvrRouter(framework.L3AgentTestFramework):
extra_routes=extra_routes,
num_internal_ports=2,
enable_gw=enable_gw,
snat_bound_fip=snat_bound_fip,
**kwargs)
internal_ports = router.get(lib_constants.INTERFACE_KEY, [])
router['distributed'] = True
router['gw_port_host'] = agent.conf.host
if enable_floating_ip:
floating_ip = router['_floatingips'][0]
for floating_ip in router[lib_constants.FLOATINGIP_KEY]:
floating_ip['host'] = agent.conf.host
if snat_bound_fip:
floating_ip[lib_constants.DVR_SNAT_BOUND] = True
if enable_floating_ip and enable_centralized_fip:
# For centralizing the fip, we are emulating the legacy
# router behavior were the fip dict does not contain any
# host information.
floating_ip['host'] = None
router[lib_constants.FLOATINGIP_KEY][0]['host'] = None
# In order to test the mixed dvr_snat and compute scenario, we create
# two floating IPs, one is distributed, another is centralized.
# The distributed floating IP should have the host, which was
# just set to None above, then we set it back. The centralized
# floating IP has host None, and this IP will be used to test
# migration from centralized to distributed.
if snat_bound_fip:
router[lib_constants.FLOATINGIP_KEY][0]['host'] = agent.conf.host
router[lib_constants.FLOATINGIP_KEY][1][
lib_constants.DVR_SNAT_BOUND] = True
router[lib_constants.FLOATINGIP_KEY][1]['host'] = None
if enable_gw:
external_gw_port = router['gw_port']
router['gw_port'][portbindings.HOST_ID] = agent.conf.host
@ -551,11 +573,11 @@ class TestDvrRouter(framework.L3AgentTestFramework):
# the agent type the dvr supports. The namespace creation is
# dependent on the agent_type.
if enable_floating_ip:
floating_ip = router['_floatingips'][0]
floating_ip['floating_network_id'] = (
external_gw_port['network_id'])
floating_ip['port_id'] = internal_ports[0]['id']
floating_ip['status'] = 'ACTIVE'
for index, floating_ip in enumerate(router['_floatingips']):
floating_ip['floating_network_id'] = (
external_gw_port['network_id'])
floating_ip['port_id'] = internal_ports[index]['id']
floating_ip['status'] = 'ACTIVE'
self._add_fip_agent_gw_port_info_to_router(router,
external_gw_port)
@ -723,7 +745,7 @@ class TestDvrRouter(framework.L3AgentTestFramework):
router.router_id)
self.assertFalse(self._namespace_exists(namespace))
def _assert_dvr_floating_ips(self, router):
def _assert_dvr_floating_ips(self, router, snat_bound_fip=False):
# in the fip namespace:
# Check that the fg-<port-id> (floatingip_agent_gateway)
# is created with the ip address of the external gateway port
@ -761,8 +783,12 @@ class TestDvrRouter(framework.L3AgentTestFramework):
# correctly
for fip in floating_ips:
expected_rules = router.floating_forward_rules(fip)
if fip.get(lib_constants.DVR_SNAT_BOUND):
iptables_mgr = router.snat_iptables_manager
else:
iptables_mgr = router.iptables_manager
self._assert_iptables_rules_exist(
router.iptables_manager, 'nat', expected_rules)
iptables_mgr, 'nat', expected_rules)
def test_dvr_router_with_ha_for_fip_disassociation(self):
"""Test to validate the fip rules are deleted in dvr_snat_ha router.
@ -890,6 +916,7 @@ class TestDvrRouter(framework.L3AgentTestFramework):
for rule in expected_rules:
self.assertIn(
str(iptables_manager.IptablesRule(rule[0], rule[1])), rules)
return True
def test_prevent_snat_rule_exist_on_restarted_agent(self):
self.agent.conf.agent_mode = 'dvr_snat'
@ -934,8 +961,7 @@ class TestDvrRouter(framework.L3AgentTestFramework):
# existing ports on the uplinked subnet, the ARP
# cache is properly populated.
self.agent.conf.agent_mode = 'dvr_snat'
router_info = l3_test_common.prepare_router_data()
router_info['distributed'] = True
router_info = self.generate_dvr_router_info(enable_snat=True)
expected_neighbor = '35.4.1.10'
port_data = {
'fixed_ips': [{'ip_address': expected_neighbor}],
@ -1082,8 +1108,9 @@ class TestDvrRouter(framework.L3AgentTestFramework):
# 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)
if fip.get(lib_constants.DVR_SNAT_BOUND):
self._assert_iptables_rules_exist(
router1.snat_iptables_manager, 'nat', expected_rules)
def test_floating_ip_migration_from_unbound_to_bound(self):
"""Test to check floating ips migrate from unboun to bound host."""
@ -1092,33 +1119,51 @@ class TestDvrRouter(framework.L3AgentTestFramework):
enable_floating_ip=True, enable_centralized_fip=True,
enable_snat=True, snat_bound_fip=True)
router1 = self.manage_router(self.agent, router_info)
centralized_floatingips = router_info[lib_constants.FLOATINGIP_KEY]
floatingips = router_info[lib_constants.FLOATINGIP_KEY]
distributed_fip = floatingips[0]
centralized_floatingip = floatingips[1]
# For private ports hosted in dvr_no_fip agent, the floatingip
# dict will contain the fip['host'] key, but the value will always
# be None to emulate the legacy router.
self.assertIsNone(centralized_floatingips[0]['host'])
self.assertIsNone(centralized_floatingip['host'])
self.assertTrue(self._namespace_exists(router1.ns_name))
fip_ns = router1.fip_ns.get_name()
self.assertTrue(self._namespace_exists(fip_ns))
self._assert_snat_namespace_exists(router1)
# If fips are centralized then, the DNAT rules are only
# configured in the SNAT Namespace and not in the router-ns.
router_ns = router1.ns_name
fixed_ip = centralized_floatingips[0]['fixed_ip_address']
for fip in centralized_floatingips:
expected_rules = router1.floating_forward_rules(fip)
self.assertFalse(self._assert_iptables_rules_exist(
router1.snat_iptables_manager, 'nat', expected_rules))
self.assertFalse(self._fixed_ip_rule_exists(router_ns, fixed_ip))
# Now let us edit the floatingIP info with 'host' and remove
# the 'dvr_snat_bound'
router1.router[lib_constants.FLOATINGIP_KEY][0]['host'] = (
expected_rules = router1.floating_forward_rules(distributed_fip)
self.assertTrue(self._assert_iptables_rules_exist(
router1.iptables_manager, 'nat', expected_rules))
expected_rules = router1._centralized_floating_forward_rules(
centralized_floatingip['floating_ip_address'],
centralized_floatingip['fixed_ip_address'])
self.assertTrue(self._assert_iptables_rules_exist(
router1.snat_iptables_manager, 'nat', expected_rules))
qrouter_ns = router1.ns_name
fixed_ip_dist = distributed_fip['fixed_ip_address']
snat_ns = router1.snat_namespace.name
fixed_ip_cent = centralized_floatingip['fixed_ip_address']
self.assertFalse(self._fixed_ip_rule_exists(qrouter_ns, fixed_ip_cent))
self.assertTrue(self._fixed_ip_rule_exists(qrouter_ns, fixed_ip_dist))
self.assertFalse(self._fixed_ip_rule_exists(snat_ns, fixed_ip_dist))
self.assertFalse(self._fixed_ip_rule_exists(snat_ns, fixed_ip_cent))
# Now let us edit the centralized floatingIP info with 'host'
# and remove the 'dvr_snat_bound'
router1.router[lib_constants.FLOATINGIP_KEY][1]['host'] = (
self.agent.conf.host)
del router1.router[lib_constants.FLOATINGIP_KEY][0]['dvr_snat_bound']
del router1.router[lib_constants.FLOATINGIP_KEY][1]['dvr_snat_bound']
self.agent._process_updated_router(router1.router)
router_updated = self.agent.router_info[router_info['id']]
router_ns = router_updated.ns_name
self.assertTrue(self._fixed_ip_rule_exists(router_ns, fixed_ip))
qrouter_ns = router_updated.ns_name
fixed_ip_dist = distributed_fip['fixed_ip_address']
snat_ns = router_updated.snat_namespace.name
fixed_ip_cent = centralized_floatingip['fixed_ip_address']
self.assertTrue(self._fixed_ip_rule_exists(qrouter_ns, fixed_ip_dist))
self.assertFalse(self._fixed_ip_rule_exists(snat_ns, fixed_ip_dist))
self.assertTrue(self._fixed_ip_rule_exists(qrouter_ns, fixed_ip_cent))
self.assertFalse(self._fixed_ip_rule_exists(snat_ns, fixed_ip_cent))
self.assertTrue(self._namespace_exists(fip_ns))
def test_floating_ip_not_deployed_on_dvr_no_external_agent(self):
@ -1140,8 +1185,7 @@ class TestDvrRouter(framework.L3AgentTestFramework):
# configured in the SNAT Namespace and not in the router-ns.
for fip in centralized_floatingips:
expected_rules = router1.floating_forward_rules(fip)
self.assertFalse(self._assert_iptables_rules_exist(
router1.iptables_manager, 'nat', expected_rules))
self.assertEqual(0, len(expected_rules))
def test_floating_ip_create_does_not_raise_keyerror_on_missing_host(self):
"""Test to check floating ips configure does not raise Keyerror."""

View File

@ -702,6 +702,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
return_value=sn_port)
ri._snat_redirect_remove = mock.Mock()
if router.get('distributed'):
ri.snat_iptables_manager = iptables_manager.IptablesManager(
namespace=ri.snat_namespace.name, use_ipv6=ri.use_ipv6)
ri.fip_ns.delete_rtr_2_fip_link = mock.Mock()
ri.router['gw_port'] = ""
ri.external_gateway_removed(ex_gw_port, interface_name)
@ -1034,6 +1036,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
self._set_ri_kwargs(agent, router['id'], router)
ri = dvr_router.DvrEdgeRouter(HOSTNAME, **self.ri_kwargs)
ri.snat_iptables_manager = iptables_manager.IptablesManager(
namespace=ri.snat_namespace.name, use_ipv6=ri.use_ipv6)
subnet_id = l3_test_common.get_subnet_id(
router[lib_constants.INTERFACE_KEY][0])
ri.router['distributed'] = True
@ -1041,15 +1045,17 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
'fixed_ips': [{'subnet_id': subnet_id,
'ip_address': '1.2.3.4'}]}]
ri.router['gw_port_host'] = None
self._test_process_router(ri, agent)
self._test_process_router(ri, agent, is_dvr_edge=True)
def _test_process_router(self, ri, agent):
def _test_process_router(self, ri, agent, is_dvr_edge=False):
router = ri.router
agent.host = HOSTNAME
fake_fip_id = 'fake_fip_id'
ri.create_dvr_external_gateway_on_agent = mock.Mock()
ri.process_floating_ip_addresses = mock.Mock()
ri.process_floating_ip_nat_rules = mock.Mock()
ri.process_floating_ip_nat_rules_for_centralized_floatingip = (
mock.Mock())
ri.process_floating_ip_addresses.return_value = {
fake_fip_id: 'ACTIVE'}
ri.external_gateway_added = mock.Mock()
@ -1064,8 +1070,14 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
ri.process()
ri.process_floating_ip_addresses.assert_called_with(mock.ANY)
ri.process_floating_ip_addresses.reset_mock()
ri.process_floating_ip_nat_rules.assert_called_with()
ri.process_floating_ip_nat_rules.reset_mock()
if not is_dvr_edge:
ri.process_floating_ip_nat_rules.assert_called_with()
ri.process_floating_ip_nat_rules.reset_mock()
elif ri.router.get('gw_port_host') == agent.host:
ri.process_floating_ip_nat_rules_for_centralized_floatingip. \
assert_called_with()
ri.process_floating_ip_nat_rules_for_centralized_floatingip. \
reset_mock()
ri.external_gateway_added.reset_mock()
# remap floating IP to a new fixed ip
@ -1076,8 +1088,14 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
ri.process()
ri.process_floating_ip_addresses.assert_called_with(mock.ANY)
ri.process_floating_ip_addresses.reset_mock()
ri.process_floating_ip_nat_rules.assert_called_with()
ri.process_floating_ip_nat_rules.reset_mock()
if not is_dvr_edge:
ri.process_floating_ip_nat_rules.assert_called_with()
ri.process_floating_ip_nat_rules.reset_mock()
elif ri.router.get('gw_port_host') == agent.host:
ri.process_floating_ip_nat_rules_for_centralized_floatingip. \
assert_called_with()
ri.process_floating_ip_nat_rules_for_centralized_floatingip. \
reset_mock()
self.assertEqual(0, ri.external_gateway_added.call_count)
self.assertEqual(0, ri.external_gateway_updated.call_count)
ri.external_gateway_added.reset_mock()
@ -1101,8 +1119,14 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
ri.process()
ri.process_floating_ip_addresses.assert_called_with(mock.ANY)
ri.process_floating_ip_addresses.reset_mock()
ri.process_floating_ip_nat_rules.assert_called_with()
ri.process_floating_ip_nat_rules.reset_mock()
if not is_dvr_edge:
ri.process_floating_ip_nat_rules.assert_called_with()
ri.process_floating_ip_nat_rules.reset_mock()
elif ri.router.get('gw_port_host') == agent.host:
ri.process_floating_ip_nat_rules_for_centralized_floatingip. \
assert_called_with()
ri.process_floating_ip_nat_rules_for_centralized_floatingip. \
reset_mock()
# now no ports so state is torn down
del router[lib_constants.INTERFACE_KEY]
@ -2267,6 +2291,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
num_internal_ports=1)
self._set_ri_kwargs(agent, router['id'], router)
ri = dvr_router.DvrEdgeRouter(HOSTNAME, **self.ri_kwargs)
ri.snat_iptables_manager = iptables_manager.IptablesManager(
namespace=ri.snat_namespace.name, use_ipv6=ri.use_ipv6)
self.mock_ip.get_devices.return_value = stale_devlist
ri.process()
@ -2783,6 +2809,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
agent._fetch_external_net_id = mock.Mock(return_value=external_net_id)
ri.ex_gw_port = ri.router['gw_port']
del ri.router['gw_port']
ri.external_gateway_added(
ri.ex_gw_port,
ri.get_external_device_name(ri.ex_gw_port['id']))
ri.fip_ns = None
nat = ri.iptables_manager.ipv4['nat']
nat.clear_rules_by_tag = mock.Mock()

View File

@ -736,6 +736,9 @@ class TestDvrRouterOperations(base.BaseTestCase):
agent._fetch_external_net_id = mock.Mock(return_value=external_net_id)
ri.ex_gw_port = ri.router['gw_port']
del ri.router['gw_port']
ri.external_gateway_added(
ri.ex_gw_port,
ri.get_external_device_name(ri.ex_gw_port['id']))
ri.fip_ns = None
nat = ri.iptables_manager.ipv4['nat']
nat.clear_rules_by_tag = mock.Mock()