]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Encapsulate DVR Fip namespace
authorCarl Baldwin <carl.baldwin@hp.com>
Wed, 7 Jan 2015 19:25:41 +0000 (19:25 +0000)
committerCarl Baldwin <carl.baldwin@hp.com>
Sat, 31 Jan 2015 00:02:54 +0000 (00:02 +0000)
The floating ip namespace is an important concept that links a DVR
enabled L3 agent to DVR routers.  The namespace is shared by all of
the dvr routers on a compute host connected to the same network.  It
is managed by the dvr enabled agent.  Hence, it should be known to the
agent and made available to the DVR routers as well.  This calls for a
proper encapsulation of this functionality in a class which can be
accessed by both the agent and the routers.

In trying to tease floating ip functionality out of the L3 agent and
in to the new router classes, I found it very difficult to do because
the logic around the floating ip namespace has not yet been properly
encapsulated.

Change-Id: I0913cfae391164146bbb50ed42311506d6559e3f
Partially-Implements: bp/restructure-l3-agent

neutron/agent/l3/agent.py
neutron/agent/l3/dvr.py
neutron/agent/l3/dvr_fip_ns.py [new file with mode: 0644]
neutron/agent/l3/dvr_router.py
neutron/tests/functional/agent/test_l3_agent.py
neutron/tests/unit/agent/test_dvr_fip_ns.py [new file with mode: 0644]
neutron/tests/unit/test_l3_agent.py

index 6020ce6a9d6c43aac6595765963148d6bdcb52c3..ab0b84cfbea4cdf49bc73cd24878c1cc0c71b24c 100644 (file)
@@ -23,6 +23,7 @@ from oslo.utils import timeutils
 
 from neutron.agent.common import config
 from neutron.agent.l3 import dvr
+from neutron.agent.l3 import dvr_fip_ns
 from neutron.agent.l3 import dvr_router
 from neutron.agent.l3 import event_observers
 from neutron.agent.l3 import ha
@@ -274,7 +275,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
     def _destroy_namespace(self, ns):
         if ns.startswith(NS_PREFIX):
             self._destroy_router_namespace(ns)
-        elif ns.startswith(dvr.FIP_NS_PREFIX):
+        elif ns.startswith(dvr_fip_ns.FIP_NS_PREFIX):
             self._destroy_fip_namespace(ns)
         elif ns.startswith(dvr.SNAT_NS_PREFIX):
             self._destroy_snat_namespace(ns)
@@ -294,7 +295,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
                 # device is on default bridge
                 self.driver.unplug(d.name, namespace=ns,
                                    prefix=INTERNAL_DEV_PREFIX)
-            elif d.name.startswith(dvr.ROUTER_2_FIP_DEV_PREFIX):
+            elif d.name.startswith(dvr_fip_ns.ROUTER_2_FIP_DEV_PREFIX):
                 ns_ip.del_veth(d.name)
             elif d.name.startswith(EXTERNAL_DEV_PREFIX):
                 self.driver.unplug(d.name,
@@ -588,7 +589,10 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         # TODO(mrsmith) - we shouldn't need to check here
         if 'distributed' not in ri.router:
             ri.router['distributed'] = False
-        self.scan_fip_ports(ri)
+        ex_gw_port = self._get_ex_gw_port(ri)
+        if ri.router.get('distributed') and ex_gw_port:
+            ri.fip_ns = self.get_fip_ns(ex_gw_port['network_id'])
+            ri.fip_ns.scan_fip_ports(ri)
         self._process_internal_ports(ri)
         self._process_external(ri)
         # Process static routes for router
@@ -598,7 +602,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         self._process_ha_router(ri)
 
         # Update ex_gw_port and enable_snat on the router info cache
-        ri.ex_gw_port = self._get_ex_gw_port(ri)
+        ri.ex_gw_port = ex_gw_port
         ri.snat_ports = ri.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, [])
         ri.enable_snat = ri.router.get('enable_snat')
 
@@ -660,25 +664,32 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
     def create_dvr_fip_interfaces(self, ri, ex_gw_port):
         floating_ips = self.get_floating_ips(ri)
         if floating_ips:
-            is_first = self._fip_ns_subscribe(ri.router_id)
+            is_first = ri.fip_ns.subscribe(ri.router_id)
             if is_first:
-                self._create_agent_gateway_port(ri, floating_ips[0]
-                                                ['floating_network_id'])
-
-        if self.agent_gateway_port and floating_ips:
-            fip_net_id = floating_ips[0]['floating_network_id']
-            self.create_rtr_2_fip_link(ri, fip_net_id)
+                agent_gateway_port = (
+                    self.plugin_rpc.get_agent_gateway_port(
+                        self.context, ex_gw_port['network_id']))
+                if 'subnet' not in agent_gateway_port:
+                    LOG.error(_LE('Missing subnet/agent_gateway_port'))
+                else:
+                    self._set_subnet_info(agent_gateway_port)
+                    ri.fip_ns.create_gateway_port(agent_gateway_port)
+
+        if ri.fip_ns.agent_gateway_port and floating_ips:
+            if ri.dist_fip_count == 0:
+                ri.fip_ns.create_rtr_2_fip_link(ri)
+
+                # kicks the FW Agent to add rules for the IR namespace if
+                # configured
+                self.process_router_add(ri)
 
     def _get_external_device_interface_name(self, ri, ex_gw_port):
         if ri.router['distributed']:
-            fip_int = self.get_fip_int_device_name(ri.router_id)
-            # TODO(mrsmith) refactor for multiple ext nets
-            fip_ns = self.get_fip_ns_name(str(self._fetch_external_net_id()))
-
+            fip_int = ri.fip_ns.get_int_device_name(ri.router_id)
             if ip_lib.device_exists(fip_int,
                                     root_helper=self.root_helper,
-                                    namespace=fip_ns):
-                return self.get_rtr_int_device_name(ri.router_id)
+                                    namespace=ri.fip_ns.get_name()):
+                return ri.fip_ns.get_rtr_ext_device_name(ri.router_id)
         else:
             return self.get_external_device_name(ex_gw_port['id'])
 
index ab35d9920cde99d240d670f810e8f44184f93eff..fceb3f5731eb10614496dac635ad16fe3ab39a6d 100644 (file)
 
 import binascii
 import netaddr
-import os
 
-from neutron.agent.l3 import link_local_allocator as lla
+from neutron.agent.l3 import dvr_fip_ns
 from neutron.agent.linux import ip_lib
 from neutron.agent.linux import iptables_manager
 from neutron.common import constants as l3_constants
-from neutron.common import utils as common_utils
 from neutron.i18n import _LE
 from neutron.openstack.common import log as logging
 
 LOG = logging.getLogger(__name__)
 
 SNAT_INT_DEV_PREFIX = 'sg-'
-FIP_EXT_DEV_PREFIX = 'fg-'
-FIP_NS_PREFIX = 'fip-'
 SNAT_NS_PREFIX = 'snat-'
-FIP_2_ROUTER_DEV_PREFIX = 'fpr-'
-ROUTER_2_FIP_DEV_PREFIX = 'rfp-'
-FIP_LL_SUBNET = '169.254.30.0/23'
-# Rule priority range for FIPs
-FIP_PR_START = 32768
-FIP_PR_END = FIP_PR_START + 40000
-# Route Table index for FIPs
-FIP_RT_TBL = 16
 # xor-folding mask used for IPv6 rule index
 MASK_30 = 0x3fffffff
 
@@ -45,24 +33,23 @@ MASK_30 = 0x3fffffff
 class AgentMixin(object):
     def __init__(self, host):
         # dvr data
-        self.agent_gateway_port = None
-        self.fip_ns_subscribers = set()
-        self.local_subnets = lla.LinkLocalAllocator(
-            os.path.join(self.conf.state_path, 'fip-linklocal-networks'),
-            FIP_LL_SUBNET)
-        self.fip_priorities = set(range(FIP_PR_START,
-                                        FIP_PR_END))
-
+        self._fip_namespaces = {}
         super(AgentMixin, self).__init__(host)
 
-    def _fip_ns_subscribe(self, router_id):
-        is_first = (len(self.fip_ns_subscribers) == 0)
-        self.fip_ns_subscribers.add(router_id)
-        return is_first
+    def get_fip_ns(self, ext_net_id):
+        # TODO(Carl) is this necessary?  Code that this replaced was careful to
+        # convert these to string like this so I preserved that.
+        ext_net_id = str(ext_net_id)
+
+        if ext_net_id not in self._fip_namespaces:
+            fip_ns = dvr_fip_ns.FipNamespace(ext_net_id,
+                                             self.conf,
+                                             self.driver,
+                                             self.root_helper,
+                                             self.use_ipv6)
+            self._fip_namespaces[ext_net_id] = fip_ns
 
-    def _fip_ns_unsubscribe(self, router_id):
-        self.fip_ns_subscribers.discard(router_id)
-        return len(self.fip_ns_subscribers) == 0
+        return self._fip_namespaces[ext_net_id]
 
     def _destroy_snat_namespace(self, ns):
         ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=ns)
@@ -79,24 +66,10 @@ class AgentMixin(object):
             self._delete_namespace(ns_ip, ns)
 
     def _destroy_fip_namespace(self, ns):
-        ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=ns)
-        for d in ns_ip.get_devices(exclude_loopback=True):
-            if d.name.startswith(FIP_2_ROUTER_DEV_PREFIX):
-                # internal link between IRs and FIP NS
-                ns_ip.del_veth(d.name)
-            elif d.name.startswith(FIP_EXT_DEV_PREFIX):
-                # single port from FIP NS to br-ext
-                # TODO(carl) Where does the port get deleted?
-                LOG.debug('DVR: unplug: %s', d.name)
-                self.driver.unplug(d.name,
-                                   bridge=self.conf.external_network_bridge,
-                                   namespace=ns,
-                                   prefix=FIP_EXT_DEV_PREFIX)
-        LOG.debug('DVR: destroy fip ns: %s', ns)
-        # TODO(mrsmith): add LOG warn if fip count != 0
-        if self.conf.router_delete_namespaces:
-            self._delete_namespace(ns_ip, ns)
-        self.agent_gateway_port = None
+        ex_net_id = ns[len(dvr_fip_ns.FIP_NS_PREFIX):]
+        fip_ns = self.get_fip_ns(ex_net_id)
+        del self._fip_namespaces[ex_net_id]
+        fip_ns.destroy()
 
     def _set_subnet_arp_info(self, ri, port):
         """Set ARP info retrieved from Plugin for existing ports."""
@@ -123,43 +96,10 @@ class AgentMixin(object):
                 if f['subnet_id'] == subnet_id:
                     return port
 
-    def scan_fip_ports(self, ri):
-        # don't scan if not dvr or count is not None
-        if not ri.router.get('distributed') or ri.dist_fip_count is not None:
-            return
-
-        # scan system for any existing fip ports
-        ri.dist_fip_count = 0
-        rtr_2_fip_interface = self.get_rtr_int_device_name(ri.router_id)
-        if ip_lib.device_exists(rtr_2_fip_interface,
-                                root_helper=self.root_helper,
-                                namespace=ri.ns_name):
-            device = ip_lib.IPDevice(rtr_2_fip_interface, self.root_helper,
-                                     namespace=ri.ns_name)
-            existing_cidrs = [addr['cidr'] for addr in device.addr.list()]
-            fip_cidrs = [c for c in existing_cidrs if
-                         common_utils.is_cidr_host(c)]
-            ri.dist_fip_count = len(fip_cidrs)
-
-    def get_fip_ext_device_name(self, port_id):
-        return (FIP_EXT_DEV_PREFIX +
-                port_id)[:self.driver.DEV_NAME_LEN]
-
-    def get_rtr_int_device_name(self, router_id):
-        return (ROUTER_2_FIP_DEV_PREFIX +
-                router_id)[:self.driver.DEV_NAME_LEN]
-
-    def get_fip_int_device_name(self, router_id):
-        return (FIP_2_ROUTER_DEV_PREFIX +
-                router_id)[:self.driver.DEV_NAME_LEN]
-
     def get_snat_int_device_name(self, port_id):
         return (SNAT_INT_DEV_PREFIX +
                 port_id)[:self.driver.DEV_NAME_LEN]
 
-    def get_fip_ns_name(self, ext_net_id):
-        return (FIP_NS_PREFIX + ext_net_id)
-
     def get_snat_ns_name(self, router_id):
         return (SNAT_NS_PREFIX + router_id)
 
@@ -205,35 +145,6 @@ class AgentMixin(object):
         else:
             LOG.error(_LE('DVR: no map match_port found!'))
 
-    def internal_ns_interface_added(self, ip_cidr,
-                                    interface_name, ns_name):
-        ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns_name)
-        ip_wrapper.netns.execute(['ip', 'addr', 'add',
-                                  ip_cidr, 'dev', interface_name])
-
-    def _handle_router_fip_nat_rules(self, ri, interface_name, action):
-        """Configures NAT rules for Floating IPs for DVR.
-
-           Remove all the rules. This is safe because if
-           use_namespaces is set as False then the agent can
-           only configure one router, otherwise each router's
-           NAT rules will be in their own namespace.
-        """
-        ri.iptables_manager.ipv4['nat'].empty_chain('POSTROUTING')
-        ri.iptables_manager.ipv4['nat'].empty_chain('snat')
-
-        # Add back the jump to float-snat
-        ri.iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat')
-
-        # And add them back if the action is add_rules
-        if action == 'add_rules' and interface_name:
-            rule = ('POSTROUTING', '! -i %(interface_name)s '
-                    '! -o %(interface_name)s -m conntrack ! '
-                    '--ctstate DNAT -j ACCEPT' %
-                    {'interface_name': interface_name})
-            ri.iptables_manager.ipv4['nat'].add_rule(*rule)
-        ri.iptables_manager.apply()
-
     def _create_dvr_gateway(self, ri, ex_gw_port, gw_interface_name,
                             snat_ports):
         """Create SNAT namespace."""
@@ -257,116 +168,23 @@ class AgentMixin(object):
         # kicks the FW Agent to add rules for the snat namespace
         self.process_router_add(ri)
 
-    def agent_gateway_added(self, ns_name, ex_gw_port,
-                            interface_name):
-        """Add Floating IP gateway port to FIP namespace."""
-        if not ip_lib.device_exists(interface_name,
-                                    root_helper=self.root_helper,
-                                    namespace=ns_name):
-            self.driver.plug(ex_gw_port['network_id'],
-                             ex_gw_port['id'], interface_name,
-                             ex_gw_port['mac_address'],
-                             bridge=self.conf.external_network_bridge,
-                             namespace=ns_name,
-                             prefix=FIP_EXT_DEV_PREFIX)
-
-        self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
-                            namespace=ns_name)
-        ip_address = ex_gw_port['ip_cidr'].split('/')[0]
-        ip_lib.send_gratuitous_arp(ns_name,
-                                   interface_name,
-                                   ip_address,
-                                   self.conf.send_arp_for_ha,
-                                   self.root_helper)
-
-        gw_ip = ex_gw_port['subnet']['gateway_ip']
-        if gw_ip:
-            ipd = ip_lib.IPDevice(interface_name, self.root_helper,
-                                  namespace=ns_name)
-            ipd.route.add_gateway(gw_ip)
-
-        cmd = ['sysctl', '-w', 'net.ipv4.conf.%s.proxy_arp=1' % interface_name]
-        ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns_name)
-        ip_wrapper.netns.execute(cmd, check_exit_code=False)
-
-    def _create_agent_gateway_port(self, ri, network_id):
-        """Create Floating IP gateway port.
-
-           Request port creation from Plugin then creates
-           Floating IP namespace and adds gateway port.
-        """
-        self.agent_gateway_port = (
-            self.plugin_rpc.get_agent_gateway_port(
-                self.context, network_id))
-        if 'subnet' not in self.agent_gateway_port:
-            LOG.error(_LE('Missing subnet/agent_gateway_port'))
-            return
-        self._set_subnet_info(self.agent_gateway_port)
-
-        # add fip-namespace and agent_gateway_port
-        fip_ns_name = (
-            self.get_fip_ns_name(str(network_id)))
-        self._create_namespace(fip_ns_name)
-        ri.fip_iptables_manager = iptables_manager.IptablesManager(
-            root_helper=self.root_helper, namespace=fip_ns_name,
-            use_ipv6=self.use_ipv6)
-        # no connection tracking needed in fip namespace
-        ri.fip_iptables_manager.ipv4['raw'].add_rule('PREROUTING',
-                                                     '-j CT --notrack')
-        ri.fip_iptables_manager.apply()
-        interface_name = (
-            self.get_fip_ext_device_name(self.agent_gateway_port['id']))
-        self.agent_gateway_added(fip_ns_name, self.agent_gateway_port,
-                                 interface_name)
-
-    def create_rtr_2_fip_link(self, ri, network_id):
-        """Create interface between router and Floating IP namespace."""
-        rtr_2_fip_name = self.get_rtr_int_device_name(ri.router_id)
-        fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id)
-        fip_ns_name = self.get_fip_ns_name(str(network_id))
-
-        # add link local IP to interface
-        if ri.rtr_fip_subnet is None:
-            ri.rtr_fip_subnet = self.local_subnets.allocate(ri.router_id)
-        rtr_2_fip, fip_2_rtr = ri.rtr_fip_subnet.get_pair()
-        ip_wrapper = ip_lib.IPWrapper(self.root_helper,
-                                      namespace=ri.ns_name)
-        if not ip_lib.device_exists(rtr_2_fip_name, self.root_helper,
-                                    namespace=ri.ns_name):
-            int_dev = ip_wrapper.add_veth(rtr_2_fip_name,
-                                          fip_2_rtr_name, fip_ns_name)
-            self.internal_ns_interface_added(str(rtr_2_fip),
-                                             rtr_2_fip_name, ri.ns_name)
-            self.internal_ns_interface_added(str(fip_2_rtr),
-                                             fip_2_rtr_name, fip_ns_name)
-            int_dev[0].link.set_up()
-            int_dev[1].link.set_up()
-        # add default route for the link local interface
-        device = ip_lib.IPDevice(rtr_2_fip_name, self.root_helper,
-                                 namespace=ri.ns_name)
-        device.route.add_gateway(str(fip_2_rtr.ip), table=FIP_RT_TBL)
-        #setup the NAT rules and chains
-        self._handle_router_fip_nat_rules(ri, rtr_2_fip_name, 'add_rules')
-        # kicks the FW Agent to add rules for the IR namespace if configured
-        self.process_router_add(ri)
-
     def floating_ip_added_dist(self, ri, fip, fip_cidr):
         """Add floating IP to FIP namespace."""
         floating_ip = fip['floating_ip_address']
         fixed_ip = fip['fixed_ip_address']
-        rule_pr = self.fip_priorities.pop()
+        rule_pr = ri.fip_ns.allocate_rule_priority()
         ri.floating_ips_dict[floating_ip] = rule_pr
-        fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id)
+        fip_2_rtr_name = ri.fip_ns.get_int_device_name(ri.router_id)
         ip_rule = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
-        ip_rule.add(fixed_ip, FIP_RT_TBL, rule_pr)
+        ip_rule.add(fixed_ip, dvr_fip_ns.FIP_RT_TBL, rule_pr)
         #Add routing rule in fip namespace
-        fip_ns_name = self.get_fip_ns_name(str(fip['floating_network_id']))
+        fip_ns_name = ri.fip_ns.get_name()
         rtr_2_fip, _ = ri.rtr_fip_subnet.get_pair()
         device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper,
                                  namespace=fip_ns_name)
         device.route.add_route(fip_cidr, str(rtr_2_fip.ip))
         interface_name = (
-            self.get_fip_ext_device_name(self.agent_gateway_port['id']))
+            ri.fip_ns.get_ext_device_name(ri.fip_ns.agent_gateway_port['id']))
         ip_lib.send_garp_for_proxyarp(fip_ns_name,
                                       interface_name,
                                       floating_ip,
@@ -378,17 +196,17 @@ class AgentMixin(object):
     def floating_ip_removed_dist(self, ri, fip_cidr):
         """Remove floating IP from FIP namespace."""
         floating_ip = fip_cidr.split('/')[0]
-        rtr_2_fip_name = self.get_rtr_int_device_name(ri.router_id)
-        fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id)
+        rtr_2_fip_name = ri.fip_ns.get_rtr_ext_device_name(ri.router_id)
+        fip_2_rtr_name = ri.fip_ns.get_int_device_name(ri.router_id)
         if ri.rtr_fip_subnet is None:
             ri.rtr_fip_subnet = self.local_subnets.allocate(ri.router_id)
         rtr_2_fip, fip_2_rtr = ri.rtr_fip_subnet.get_pair()
-        fip_ns_name = self.get_fip_ns_name(str(self._fetch_external_net_id()))
+        fip_ns_name = ri.fip_ns.get_name()
         if floating_ip in ri.floating_ips_dict:
             rule_pr = ri.floating_ips_dict[floating_ip]
             ip_rule = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
-            ip_rule.delete(floating_ip, FIP_RT_TBL, rule_pr)
-            self.fip_priorities.add(rule_pr)
+            ip_rule.delete(floating_ip, dvr_fip_ns.FIP_RT_TBL, rule_pr)
+            ri.fip_ns.deallocate_rule_priority(rule_pr)
             #TODO(rajeev): Handle else case - exception/log?
 
         device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper,
@@ -403,11 +221,11 @@ class AgentMixin(object):
                                      namespace=ri.ns_name)
             ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=fip_ns_name)
             device.route.delete_gateway(str(fip_2_rtr.ip),
-                                        table=FIP_RT_TBL)
-            self.local_subnets.release(ri.router_id)
+                                        table=dvr_fip_ns.FIP_RT_TBL)
+            ri.fip_ns.local_subnets.release(ri.router_id)
             ri.rtr_fip_subnet = None
             ns_ip.del_veth(fip_2_rtr_name)
-            is_last = self._fip_ns_unsubscribe(ri.router_id)
+            is_last = ri.fip_ns.unsubscribe(ri.router_id)
             # clean up fip-namespace if this is the last FIP
             if is_last:
                 self._destroy_fip_namespace(fip_ns_name)
diff --git a/neutron/agent/l3/dvr_fip_ns.py b/neutron/agent/l3/dvr_fip_ns.py
new file mode 100644 (file)
index 0000000..52bd59b
--- /dev/null
@@ -0,0 +1,238 @@
+# Copyright (c) 2015 Openstack Foundation
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import netaddr
+import os
+
+from neutron.agent.l3 import link_local_allocator as lla
+from neutron.agent.linux import ip_lib
+from neutron.agent.linux import iptables_manager
+from neutron.common import utils as common_utils
+from neutron.i18n import _LE
+from neutron.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+FIP_NS_PREFIX = 'fip-'
+FIP_EXT_DEV_PREFIX = 'fg-'
+FIP_2_ROUTER_DEV_PREFIX = 'fpr-'
+ROUTER_2_FIP_DEV_PREFIX = 'rfp-'
+# Route Table index for FIPs
+FIP_RT_TBL = 16
+FIP_LL_SUBNET = '169.254.30.0/23'
+# Rule priority range for FIPs
+FIP_PR_START = 32768
+FIP_PR_END = FIP_PR_START + 40000
+
+
+class FipNamespace(object):
+    def __init__(self, ext_net_id, agent_conf, driver, root_helper, use_ipv6):
+        self._ext_net_id = ext_net_id
+        self.agent_conf = agent_conf
+        self.driver = driver
+        self.root_helper = root_helper
+        self.use_ipv6 = use_ipv6
+        self.agent_gateway_port = None
+        self._subscribers = set()
+        self._rule_priorities = set(range(FIP_PR_START, FIP_PR_END))
+        self._iptables_manager = iptables_manager.IptablesManager(
+            root_helper=self.root_helper,
+            namespace=self.get_name(),
+            use_ipv6=self.use_ipv6)
+        path = os.path.join(agent_conf.state_path, 'fip-linklocal-networks')
+        self.local_subnets = lla.LinkLocalAllocator(path, FIP_LL_SUBNET)
+
+    def get_name(self):
+        return (FIP_NS_PREFIX + self._ext_net_id)
+
+    def get_ext_device_name(self, port_id):
+        return (FIP_EXT_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
+
+    def get_int_device_name(self, router_id):
+        return (FIP_2_ROUTER_DEV_PREFIX + router_id)[:self.driver.DEV_NAME_LEN]
+
+    def get_rtr_ext_device_name(self, router_id):
+        return (ROUTER_2_FIP_DEV_PREFIX + router_id)[:self.driver.DEV_NAME_LEN]
+
+    def has_subscribers(self):
+        return len(self._subscribers) != 0
+
+    def subscribe(self, router_id):
+        is_first = not self.has_subscribers()
+        self._subscribers.add(router_id)
+        return is_first
+
+    def unsubscribe(self, router_id):
+        self._subscribers.discard(router_id)
+        return not self.has_subscribers()
+
+    def allocate_rule_priority(self):
+        return self._rule_priorities.pop()
+
+    def deallocate_rule_priority(self, rule_pr):
+        self._rule_priorities.add(rule_pr)
+
+    def _gateway_added(self, ex_gw_port, interface_name):
+        """Add Floating IP gateway port."""
+        ns_name = self.get_name()
+        if not ip_lib.device_exists(interface_name,
+                                    root_helper=self.root_helper,
+                                    namespace=ns_name):
+            self.driver.plug(ex_gw_port['network_id'],
+                             ex_gw_port['id'],
+                             interface_name,
+                             ex_gw_port['mac_address'],
+                             bridge=self.agent_conf.external_network_bridge,
+                             namespace=ns_name,
+                             prefix=FIP_EXT_DEV_PREFIX)
+
+        self.driver.init_l3(interface_name,
+                            [ex_gw_port['ip_cidr']],
+                            namespace=ns_name)
+
+        ip_address = str(netaddr.IPNetwork(ex_gw_port['ip_cidr']).ip)
+        ip_lib.send_gratuitous_arp(ns_name,
+                                   interface_name,
+                                   ip_address,
+                                   self.agent_conf.send_arp_for_ha,
+                                   self.root_helper)
+
+        gw_ip = ex_gw_port['subnet']['gateway_ip']
+        if gw_ip:
+            ipd = ip_lib.IPDevice(interface_name,
+                                  self.root_helper,
+                                  namespace=ns_name)
+            ipd.route.add_gateway(gw_ip)
+
+        cmd = ['sysctl', '-w', 'net.ipv4.conf.%s.proxy_arp=1' % interface_name]
+        # TODO(Carl) mlavelle's work has self.ip_wrapper
+        ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns_name)
+        ip_wrapper.netns.execute(cmd, check_exit_code=False)
+
+    def create(self):
+        # TODO(Carl) Get this functionality from mlavelle's namespace baseclass
+        ip_wrapper_root = ip_lib.IPWrapper(self.root_helper)
+        ip_wrapper = ip_wrapper_root.ensure_namespace(self.get_name())
+        ip_wrapper.netns.execute(['sysctl', '-w', 'net.ipv4.ip_forward=1'])
+        if self.use_ipv6:
+            ip_wrapper.netns.execute(['sysctl', '-w',
+                                      'net.ipv6.conf.all.forwarding=1'])
+
+        # no connection tracking needed in fip namespace
+        self._iptables_manager.ipv4['raw'].add_rule('PREROUTING',
+                                                    '-j CT --notrack')
+        self._iptables_manager.apply()
+
+    def destroy(self):
+        ns = self.get_name()
+        # TODO(carl) Reconcile this with mlavelle's namespace work
+        # TODO(carl) mlavelle's work has self.ip_wrapper
+        ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns)
+        for d in ip_wrapper.get_devices(exclude_loopback=True):
+            if d.name.startswith(FIP_2_ROUTER_DEV_PREFIX):
+                # internal link between IRs and FIP NS
+                ip_wrapper.del_veth(d.name)
+            elif d.name.startswith(FIP_EXT_DEV_PREFIX):
+                # single port from FIP NS to br-ext
+                # TODO(carl) Where does the port get deleted?
+                LOG.debug('DVR: unplug: %s', d.name)
+                ext_net_bridge = self.agent_conf.external_network_bridge
+                self.driver.unplug(d.name,
+                                   bridge=ext_net_bridge,
+                                   namespace=ns,
+                                   prefix=FIP_EXT_DEV_PREFIX)
+        LOG.debug('DVR: destroy fip ns: %s', ns)
+        # TODO(mrsmith): add LOG warn if fip count != 0
+        if self.agent_conf.router_delete_namespaces:
+            try:
+                ip_wrapper.netns.delete(ns)
+            except RuntimeError:
+                LOG.exception(_LE('Failed trying to delete namespace: %s'), ns)
+
+        self.agent_gateway_port = None
+
+    def create_gateway_port(self, agent_gateway_port):
+        """Create Floating IP gateway port.
+
+           Request port creation from Plugin then creates
+           Floating IP namespace and adds gateway port.
+        """
+        self.agent_gateway_port = agent_gateway_port
+
+        # add fip-namespace and agent_gateway_port
+        self.create()
+
+        iface_name = self.get_ext_device_name(agent_gateway_port['id'])
+        self._gateway_added(agent_gateway_port, iface_name)
+
+    def _internal_ns_interface_added(self, ip_cidr,
+                                    interface_name, ns_name):
+        ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns_name)
+        ip_wrapper.netns.execute(['ip', 'addr', 'add',
+                                  ip_cidr, 'dev', interface_name])
+
+    def create_rtr_2_fip_link(self, ri):
+        """Create interface between router and Floating IP namespace."""
+        rtr_2_fip_name = self.get_rtr_ext_device_name(ri.router_id)
+        fip_2_rtr_name = self.get_int_device_name(ri.router_id)
+        fip_ns_name = self.get_name()
+
+        # add link local IP to interface
+        if ri.rtr_fip_subnet is None:
+            ri.rtr_fip_subnet = self.local_subnets.allocate(ri.router_id)
+        rtr_2_fip, fip_2_rtr = ri.rtr_fip_subnet.get_pair()
+        ip_wrapper = ip_lib.IPWrapper(self.root_helper,
+                                      namespace=ri.ns_name)
+        device_exists = ip_lib.device_exists(rtr_2_fip_name,
+                                             self.root_helper,
+                                             namespace=ri.ns_name)
+        if not device_exists:
+            int_dev = ip_wrapper.add_veth(rtr_2_fip_name,
+                                          fip_2_rtr_name,
+                                          fip_ns_name)
+            self._internal_ns_interface_added(str(rtr_2_fip),
+                                              rtr_2_fip_name,
+                                              ri.ns_name)
+            self._internal_ns_interface_added(str(fip_2_rtr),
+                                              fip_2_rtr_name,
+                                              fip_ns_name)
+            int_dev[0].link.set_up()
+            int_dev[1].link.set_up()
+
+        # add default route for the link local interface
+        device = ip_lib.IPDevice(rtr_2_fip_name,
+                                 self.root_helper,
+                                 namespace=ri.ns_name)
+        device.route.add_gateway(str(fip_2_rtr.ip), table=FIP_RT_TBL)
+        #setup the NAT rules and chains
+        ri._handle_fip_nat_rules(rtr_2_fip_name, 'add_rules')
+
+    def scan_fip_ports(self, ri):
+        # don't scan if not dvr or count is not None
+        if ri.dist_fip_count is not None:
+            return
+
+        # scan system for any existing fip ports
+        ri.dist_fip_count = 0
+        rtr_2_fip_interface = self.get_rtr_ext_device_name(ri.router_id)
+        if ip_lib.device_exists(rtr_2_fip_interface,
+                                root_helper=self.root_helper,
+                                namespace=ri.ns_name):
+            device = ip_lib.IPDevice(rtr_2_fip_interface,
+                                     self.root_helper,
+                                     namespace=ri.ns_name)
+            existing_cidrs = [addr['cidr'] for addr in device.addr.list()]
+            fip_cidrs = [c for c in existing_cidrs if
+                         common_utils.is_cidr_host(c)]
+            ri.dist_fip_count = len(fip_cidrs)
index 6822a15aaf722f38938b4b9d2f8fb350866788da..8ad32da9a83956f6366584d673b5c4e4967aeb00 100644 (file)
@@ -24,3 +24,26 @@ class DvrRouter(router.RouterInfo):
         # Linklocal subnet for router and floating IP namespace link
         self.rtr_fip_subnet = None
         self.dist_fip_count = None
+
+    def _handle_fip_nat_rules(self, interface_name, action):
+        """Configures NAT rules for Floating IPs for DVR.
+
+           Remove all the rules. This is safe because if
+           use_namespaces is set as False then the agent can
+           only configure one router, otherwise each router's
+           NAT rules will be in their own namespace.
+        """
+        self.iptables_manager.ipv4['nat'].empty_chain('POSTROUTING')
+        self.iptables_manager.ipv4['nat'].empty_chain('snat')
+
+        # Add back the jump to float-snat
+        self.iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat')
+
+        # And add them back if the action is add_rules
+        if action == 'add_rules' and interface_name:
+            rule = ('POSTROUTING', '! -i %(interface_name)s '
+                    '! -o %(interface_name)s -m conntrack ! '
+                    '--ctstate DNAT -j ACCEPT' %
+                    {'interface_name': interface_name})
+            self.iptables_manager.ipv4['nat'].add_rule(*rule)
+        self.iptables_manager.apply()
index 03bfaf052e325241c6b78eb6d19baa3e2a5dd6af..837bb57a4b8ccc2b87a05cdf8710ab8aab31290d 100755 (executable)
@@ -681,17 +681,16 @@ class TestDvrRouter(L3AgentTestFramework):
         self.assertTrue(floating_ips)
 
         external_port = self.agent._get_ex_gw_port(router)
-        fip_ns_name = (
-            self.agent.get_fip_ns_name(floating_ips[0]['floating_network_id'])
-        )
+        fip_ns = self.agent.get_fip_ns(floating_ips[0]['floating_network_id'])
+        fip_ns_name = fip_ns.get_name()
         fg_port_created_succesfully = ip_lib.device_exists_with_ip_mac(
-            self.agent.get_fip_ext_device_name(external_port['id']),
+            fip_ns.get_ext_device_name(external_port['id']),
             external_port['ip_cidr'],
             external_port['mac_address'],
             fip_ns_name, self.root_helper)
         self.assertTrue(fg_port_created_succesfully)
         # Check fpr-router device has been created
-        device_name = self.agent.get_fip_int_device_name(router.router_id)
+        device_name = fip_ns.get_int_device_name(router.router_id)
         fpr_router_device_created_succesfully = ip_lib.device_exists(
             device_name, self.root_helper, fip_ns_name)
         self.assertTrue(fpr_router_device_created_succesfully)
@@ -699,6 +698,6 @@ class TestDvrRouter(L3AgentTestFramework):
         # In the router namespace
         # Check rfp-<router-id> is created correctly
         for fip in floating_ips:
-            device_name = self.agent.get_rtr_int_device_name(router.router_id)
+            device_name = fip_ns.get_rtr_ext_device_name(router.router_id)
             self.assertTrue(ip_lib.device_exists(
                 device_name, self.root_helper, router.ns_name))
diff --git a/neutron/tests/unit/agent/test_dvr_fip_ns.py b/neutron/tests/unit/agent/test_dvr_fip_ns.py
new file mode 100644 (file)
index 0000000..9f4bc78
--- /dev/null
@@ -0,0 +1,188 @@
+# Copyright (c) 2015 Openstack Foundation
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import mock
+
+from neutron.agent.l3 import dvr_fip_ns
+from neutron.agent.l3 import link_local_allocator as lla
+from neutron.agent.linux import ip_lib
+from neutron.openstack.common import uuidutils
+from neutron.tests import base
+
+_uuid = uuidutils.generate_uuid
+
+
+class TestDvrFipNs(base.BaseTestCase):
+    def setUp(self):
+        super(TestDvrFipNs, self).setUp()
+        self.conf = mock.Mock()
+        self.conf.state_path = '/tmp'
+        self.driver = mock.Mock()
+        self.driver.DEV_NAME_LEN = 14
+        self.net_id = _uuid()
+        self.fip_ns = dvr_fip_ns.FipNamespace(self.net_id,
+                                              self.conf,
+                                              self.driver,
+                                              mock.sentinel.root_helper,
+                                              use_ipv6=True)
+
+    def test_subscribe(self):
+        is_first = self.fip_ns.subscribe(mock.sentinel.router_id)
+        self.assertTrue(is_first)
+
+    def test_subscribe_not_first(self):
+        self.fip_ns.subscribe(mock.sentinel.router_id)
+        is_first = self.fip_ns.subscribe(mock.sentinel.router_id2)
+        self.assertFalse(is_first)
+
+    def test_unsubscribe(self):
+        self.fip_ns.subscribe(mock.sentinel.router_id)
+        is_last = self.fip_ns.unsubscribe(mock.sentinel.router_id)
+        self.assertTrue(is_last)
+
+    def test_unsubscribe_not_last(self):
+        self.fip_ns.subscribe(mock.sentinel.router_id)
+        self.fip_ns.subscribe(mock.sentinel.router_id2)
+        is_last = self.fip_ns.unsubscribe(mock.sentinel.router_id2)
+        self.assertFalse(is_last)
+
+    def test_allocate_rule_priority(self):
+        pr = self.fip_ns.allocate_rule_priority()
+        self.assertNotIn(pr, self.fip_ns._rule_priorities)
+
+    def test_deallocate_rule_priority(self):
+        pr = self.fip_ns.allocate_rule_priority()
+        self.fip_ns.deallocate_rule_priority(pr)
+        self.assertIn(pr, self.fip_ns._rule_priorities)
+
+    @mock.patch.object(ip_lib, 'IPWrapper')
+    @mock.patch.object(ip_lib, 'IPDevice')
+    @mock.patch.object(ip_lib, 'send_gratuitous_arp')
+    @mock.patch.object(ip_lib, 'device_exists')
+    def test_gateway_added(self, device_exists, send_arp, IPDevice, IPWrapper):
+        agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
+                                        'subnet_id': _uuid()}],
+                         'subnet': {'gateway_ip': '20.0.0.1'},
+                         'id': _uuid(),
+                         'network_id': self.net_id,
+                         'mac_address': 'ca:fe:de:ad:be:ef',
+                         'ip_cidr': '20.0.0.30/24'}
+
+        device_exists.return_value = False
+        self.fip_ns._gateway_added(agent_gw_port,
+                                   mock.sentinel.interface_name)
+        self.assertEqual(self.driver.plug.call_count, 1)
+        self.assertEqual(self.driver.init_l3.call_count, 1)
+        send_arp.assert_called_once_with(self.fip_ns.get_name(),
+                                         mock.sentinel.interface_name,
+                                         '20.0.0.30',
+                                         mock.ANY, mock.ANY)
+
+    @mock.patch.object(ip_lib, 'IPWrapper')
+    def test_destroy(self, IPWrapper):
+        ip_wrapper = IPWrapper()
+        dev1 = mock.Mock()
+        dev1.name = 'fpr-aaaa'
+        dev2 = mock.Mock()
+        dev2.name = 'fg-aaaa'
+        ip_wrapper.get_devices.return_value = [dev1, dev2]
+
+        self.fip_ns.destroy()
+
+        ext_net_bridge = self.conf.external_network_bridge
+        ns_name = self.fip_ns.get_name()
+        self.driver.unplug.assert_called_once_with('fg-aaaa',
+                                                   bridge=ext_net_bridge,
+                                                   prefix='fg-',
+                                                   namespace=ns_name)
+        ip_wrapper.del_veth.assert_called_once_with('fpr-aaaa')
+
+    @mock.patch.object(ip_lib, 'IPWrapper')
+    @mock.patch.object(ip_lib, 'IPDevice')
+    @mock.patch.object(ip_lib, 'device_exists')
+    def test_create_rtr_2_fip_link(self, device_exists, IPDevice, IPWrapper):
+        ri = mock.Mock()
+        ri.router_id = _uuid()
+        ri.rtr_fip_subnet = None
+        ri.ns_name = mock.sentinel.router_ns
+
+        rtr_2_fip_name = self.fip_ns.get_rtr_ext_device_name(ri.router_id)
+        fip_2_rtr_name = self.fip_ns.get_int_device_name(ri.router_id)
+        fip_ns_name = self.fip_ns.get_name()
+
+        self.fip_ns.local_subnets = allocator = mock.Mock()
+        pair = lla.LinkLocalAddressPair('169.254.31.28/31')
+        allocator.allocate.return_value = pair
+        device_exists.return_value = False
+        self.fip_ns.create_rtr_2_fip_link(ri)
+
+        ip_wrapper = IPWrapper()
+        ip_wrapper.add_veth.assert_called_with(rtr_2_fip_name,
+                                               fip_2_rtr_name,
+                                               fip_ns_name)
+
+        device = IPDevice()
+        device.route.add_gateway.assert_called_once_with(
+            '169.254.31.29', table=16)
+
+    @mock.patch.object(ip_lib, 'IPWrapper')
+    @mock.patch.object(ip_lib, 'IPDevice')
+    @mock.patch.object(ip_lib, 'device_exists')
+    def test_create_rtr_2_fip_link_already_exists(self,
+                                                  device_exists,
+                                                  IPDevice,
+                                                  IPWrapper):
+        ri = mock.Mock()
+        ri.router_id = _uuid()
+        ri.rtr_fip_subnet = None
+        device_exists.return_value = True
+
+        self.fip_ns.local_subnets = allocator = mock.Mock()
+        pair = lla.LinkLocalAddressPair('169.254.31.28/31')
+        allocator.allocate.return_value = pair
+        self.fip_ns.create_rtr_2_fip_link(ri)
+
+        ip_wrapper = IPWrapper()
+        self.assertFalse(ip_wrapper.add_veth.called)
+
+    @mock.patch.object(ip_lib, 'IPDevice')
+    def _test_scan_fip_ports(self, ri, ip_list, IPDevice):
+        IPDevice.return_value = device = mock.Mock()
+        device.addr.list.return_value = ip_list
+        self.fip_ns.get_rtr_ext_device_name = mock.Mock(
+            return_value=mock.sentinel.rtr_ext_device_name)
+        self.fip_ns.scan_fip_ports(ri)
+
+    @mock.patch.object(ip_lib, 'device_exists')
+    def test_scan_fip_ports_restart_fips(self, device_exists):
+        device_exists.return_value = True
+        ri = mock.Mock()
+        ri.dist_fip_count = None
+        ip_list = [{'cidr': '111.2.3.4/32'}, {'cidr': '111.2.3.5/32'}]
+        self._test_scan_fip_ports(ri, ip_list)
+        self.assertEqual(2, ri.dist_fip_count)
+
+    @mock.patch.object(ip_lib, 'device_exists')
+    def test_scan_fip_ports_restart_none(self, device_exists):
+        device_exists.return_value = True
+        ri = mock.Mock()
+        ri.dist_fip_count = None
+        self._test_scan_fip_ports(ri, [])
+        self.assertEqual(0, ri.dist_fip_count)
+
+    def test_scan_fip_ports_restart_zero(self):
+        ri = mock.Mock()
+        ri.dist_fip_count = 0
+        self._test_scan_fip_ports(ri, None)
+        self.assertEqual(0, ri.dist_fip_count)
index a1cb633aeb660f21cb0689cd3312537365747a3e..6f78763638d764ab1371a6ed6a755eef597222d9 100644 (file)
@@ -26,6 +26,7 @@ from neutron.agent.common import config as agent_config
 from neutron.agent.l3 import agent as l3_agent
 from neutron.agent.l3 import config as l3_config
 from neutron.agent.l3 import dvr
+from neutron.agent.l3 import dvr_fip_ns
 from neutron.agent.l3 import dvr_router
 from neutron.agent.l3 import ha
 from neutron.agent.l3 import link_local_allocator as lla
@@ -415,19 +416,21 @@ class TestBasicRouterOperations(base.BaseTestCase):
         ri = l3router.RouterInfo(router['id'], router,
                                  ns_name=agent.get_ns_name(router['id']),
                                  **self.ri_kwargs)
+        ex_net_id = _uuid()
         # Special setup for dvr routers
         if router.get('distributed'):
             agent.conf.agent_mode = 'dvr_snat'
             agent.host = HOSTNAME
             agent._create_dvr_gateway = mock.Mock()
             agent.get_snat_interfaces = mock.Mock(return_value=self.snat_ports)
+            ri.fip_ns = agent.get_fip_ns(ex_net_id)
 
         ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
                                      'subnet_id': _uuid()}],
                       'subnet': {'gateway_ip': '20.0.0.1'},
                       'extra_subnets': [{'cidr': '172.16.0.0/24'}],
                       'id': _uuid(),
-                      'network_id': _uuid(),
+                      'network_id': ex_net_id,
                       'mac_address': 'ca:fe:de:ad:be:ef',
                       'ip_cidr': '20.0.0.30/24'}
         interface_name = agent.get_external_device_name(ex_gw_port['id'])
@@ -825,39 +828,6 @@ class TestBasicRouterOperations(base.BaseTestCase):
             4, '1.5.25.15', '00:44:33:22:11:55')
         agent.router_deleted(None, router['id'])
 
-    @mock.patch('neutron.agent.linux.ip_lib.IPDevice')
-    def _test_scan_fip_ports(self, ri, ip_list, IPDevice):
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        self.device_exists.return_value = True
-        IPDevice.return_value = device = mock.Mock()
-        device.addr.list.return_value = ip_list
-        agent.scan_fip_ports(ri)
-
-    def test_scan_fip_ports_restart_fips(self):
-        router = prepare_router_data()
-        ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
-        ri.router['distributed'] = True
-        ip_list = [{'cidr': '111.2.3.4/32'}, {'cidr': '111.2.3.5/32'}]
-        self._test_scan_fip_ports(ri, ip_list)
-        self.assertEqual(ri.dist_fip_count, 2)
-
-    def test_scan_fip_ports_restart_none(self):
-        router = prepare_router_data()
-        ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
-        ri.router['distributed'] = True
-        ip_list = []
-        self._test_scan_fip_ports(ri, ip_list)
-        self.assertEqual(ri.dist_fip_count, 0)
-
-    def test_scan_fip_ports_restart_zero(self):
-        router = prepare_router_data()
-        ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
-        ri.router['distributed'] = True
-        ri.dist_fip_count = 0
-        ip_list = None
-        self._test_scan_fip_ports(ri, ip_list)
-        self.assertEqual(ri.dist_fip_count, 0)
-
     def test_process_cent_router(self):
         router = prepare_router_data()
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
@@ -961,10 +931,11 @@ class TestBasicRouterOperations(base.BaseTestCase):
         IPDevice.return_value = device = mock.Mock()
         device.addr.list.return_value = []
         ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
-        ex_gw_port = {'id': _uuid()}
+        ex_gw_port = {'id': _uuid(), 'network_id': mock.sentinel.ext_net_id}
 
         with mock.patch.object(lla.LinkLocalAllocator, '_write'):
             if ri.router['distributed']:
+                ri.fip_ns = agent.get_fip_ns(ex_gw_port['network_id'])
                 agent.create_dvr_fip_interfaces(ri, ex_gw_port)
             fip_statuses = agent.process_router_floating_ip_addresses(
                 ri, ex_gw_port)
@@ -1015,13 +986,13 @@ class TestBasicRouterOperations(base.BaseTestCase):
              'host': HOSTNAME,
              'floating_ip_address': '15.1.2.3',
              'fixed_ip_address': '192.168.0.1',
-             'floating_network_id': _uuid(),
+             'floating_network_id': mock.sentinel.ext_net_id,
              'port_id': _uuid()},
             {'id': _uuid(),
              'host': 'some-other-host',
              'floating_ip_address': '15.1.2.4',
              'fixed_ip_address': '192.168.0.10',
-             'floating_network_id': _uuid(),
+             'floating_network_id': mock.sentinel.ext_net_id,
              'port_id': _uuid()}]}
 
         router = prepare_router_data(enable_snat=True)
@@ -1032,7 +1003,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
         ri.dist_fip_count = 0
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         agent.host = HOSTNAME
-        agent.agent_gateway_port = (
+        fip_ns = agent.get_fip_ns(mock.sentinel.ext_net_id)
+        fip_ns.agent_gateway_port = (
             {'fixed_ips': [{'ip_address': '20.0.0.30',
              'subnet_id': _uuid()}],
              'subnet': {'gateway_ip': '20.0.0.1'},
@@ -1140,7 +1112,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
 
         fip_statuses = agent.process_router_floating_ip_addresses(
-            ri, {'id': _uuid()})
+            ri, {'id': _uuid(), 'network_id': mock.sentinel.ext_net_id})
 
         self.assertIsNone(fip_statuses.get(fip_id))
 
@@ -1617,23 +1589,6 @@ class TestBasicRouterOperations(base.BaseTestCase):
         agent.router_added_to_agent(None, [FAKE_ID])
         self.assertEqual(1, agent._queue.add.call_count)
 
-    def test_destroy_fip_namespace(self):
-        namespaces = ['qrouter-foo', 'qrouter-bar']
-
-        self.mock_ip.get_namespaces.return_value = namespaces
-        self.mock_ip.get_devices.return_value = [FakeDev('fpr-aaaa'),
-                                                 FakeDev('fg-aaaa')]
-
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-
-        agent._destroy_fip_namespace(namespaces[0])
-        self.mock_driver.unplug.assert_called_once_with('fg-aaaa',
-                                                        bridge='br-ex',
-                                                        prefix='fg-',
-                                                        namespace='qrouter'
-                                                        '-foo')
-        self.mock_ip.del_veth.assert_called_once_with('fpr-aaaa')
-
     def test_destroy_namespace(self):
         namespace = 'qrouter-bar'
 
@@ -1910,81 +1865,16 @@ class TestBasicRouterOperations(base.BaseTestCase):
         self.assertEqual(self.mock_driver.plug.call_count, 3)
         self.assertEqual(self.mock_driver.init_l3.call_count, 3)
 
-    def test_agent_gateway_added(self):
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        network_id = _uuid()
-        port_id = _uuid()
-        agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
-                                        'subnet_id': _uuid()}],
-                         'subnet': {'gateway_ip': '20.0.0.1'},
-                         'id': port_id,
-                         'network_id': network_id,
-                         'mac_address': 'ca:fe:de:ad:be:ef',
-                         'ip_cidr': '20.0.0.30/24'}
-        fip_ns_name = (
-            agent.get_fip_ns_name(str(network_id)))
-        interface_name = (
-            agent.get_fip_ext_device_name(port_id))
-
-        self.device_exists.return_value = False
-        agent.agent_gateway_added(fip_ns_name, agent_gw_port,
-                                  interface_name)
-        self.assertEqual(self.mock_driver.plug.call_count, 1)
-        self.assertEqual(self.mock_driver.init_l3.call_count, 1)
-        if self.conf.use_namespaces:
-            self.send_arp.assert_called_once_with(fip_ns_name, interface_name,
-                                                  '20.0.0.30',
-                                                  mock.ANY, mock.ANY)
-        else:
-            self.utils_exec.assert_any_call(
-                check_exit_code=True, root_helper=self.conf.root_helper)
-
-    def test_create_rtr_2_fip_link(self):
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        router = prepare_router_data()
-        fip = {'id': _uuid(),
-               'host': HOSTNAME,
-               'floating_ip_address': '15.1.2.3',
-               'fixed_ip_address': '192.168.0.1',
-               'floating_network_id': _uuid(),
-               'port_id': _uuid()}
-
-        ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
-
-        rtr_2_fip_name = agent.get_rtr_int_device_name(ri.router_id)
-        fip_2_rtr_name = agent.get_fip_int_device_name(ri.router_id)
-        fip_ns_name = agent.get_fip_ns_name(str(fip['floating_network_id']))
-
-        with mock.patch.object(lla.LinkLocalAllocator, '_write'):
-            self.device_exists.return_value = False
-            agent.create_rtr_2_fip_link(ri, fip['floating_network_id'])
-        self.mock_ip.add_veth.assert_called_with(rtr_2_fip_name,
-                                                 fip_2_rtr_name, fip_ns_name)
-        # TODO(mrsmith): add more aasserts -
-        self.mock_ip_dev.route.add_gateway.assert_called_once_with(
-            '169.254.31.29', table=16)
-
-    # TODO(mrsmith): test _create_agent_gateway_port
-
-    def test_create_rtr_2_fip_link_already_exists(self):
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        router = prepare_router_data()
-
-        ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
-        self.device_exists.return_value = True
-        with mock.patch.object(lla.LinkLocalAllocator, '_write'):
-            agent.create_rtr_2_fip_link(ri, {})
-        self.assertFalse(self.mock_ip.add_veth.called)
-
     def test_floating_ip_added_dist(self):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router = prepare_router_data()
         ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
+        ext_net_id = _uuid()
         agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
                                         'subnet_id': _uuid()}],
                          'subnet': {'gateway_ip': '20.0.0.1'},
                          'id': _uuid(),
-                         'network_id': _uuid(),
+                         'network_id': ext_net_id,
                          'mac_address': 'ca:fe:de:ad:be:ef',
                          'ip_cidr': '20.0.0.30/24'}
 
@@ -1992,9 +1882,10 @@ class TestBasicRouterOperations(base.BaseTestCase):
                'host': HOSTNAME,
                'floating_ip_address': '15.1.2.3',
                'fixed_ip_address': '192.168.0.1',
-               'floating_network_id': _uuid(),
+               'floating_network_id': ext_net_id,
                'port_id': _uuid()}
-        agent.agent_gateway_port = agent_gw_port
+        ri.fip_ns = agent.get_fip_ns(ext_net_id)
+        ri.fip_ns.agent_gateway_port = agent_gw_port
         ri.rtr_fip_subnet = lla.LinkLocalAddressPair('169.254.30.42/31')
         ri.dist_fip_count = 0
         ip_cidr = common_utils.ip_to_cidr(fip['floating_ip_address'])
@@ -2002,9 +1893,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
         self.mock_rule.add.assert_called_with('192.168.0.1', 16, FIP_PRI)
         # TODO(mrsmith): add more asserts
 
-    @mock.patch.object(l3_agent.L3NATAgent, '_fip_ns_unsubscribe')
     @mock.patch.object(lla.LinkLocalAllocator, '_write')
-    def test_floating_ip_removed_dist(self, write, unsubscribe):
+    def test_floating_ip_removed_dist(self, write):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router = prepare_router_data()
         agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
@@ -2018,7 +1908,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
 
         ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
         ri.dist_fip_count = 2
-        agent.fip_ns_subscribers.add(ri.router_id)
+        ri.fip_ns = agent.get_fip_ns(agent._fetch_external_net_id())
+        ri.fip_ns.unsubscribe = mock.Mock()
         ri.floating_ips_dict['11.22.33.44'] = FIP_PRI
         ri.fip_2_rtr = '11.22.33.42'
         ri.rtr_2_fip = '11.22.33.40'
@@ -2030,21 +1921,20 @@ class TestBasicRouterOperations(base.BaseTestCase):
         self.mock_rule.delete.assert_called_with(floating_ip, 16, FIP_PRI)
         self.mock_ip_dev.route.delete_route.assert_called_with(fip_cidr,
                                                                str(s.ip))
-        self.assertFalse(unsubscribe.called, '_fip_ns_unsubscribe called!')
+        self.assertFalse(ri.fip_ns.unsubscribe.called)
 
         with mock.patch.object(agent, '_destroy_fip_namespace') as f:
             ri.dist_fip_count = 1
-            fip_ns_name = agent.get_fip_ns_name(
-                str(agent._fetch_external_net_id()))
-            ri.rtr_fip_subnet = agent.local_subnets.allocate(ri.router_id)
+            fip_ns_name = ri.fip_ns.get_name()
+            ri.rtr_fip_subnet = ri.fip_ns.local_subnets.allocate(ri.router_id)
             _, fip_to_rtr = ri.rtr_fip_subnet.get_pair()
             agent.floating_ip_removed_dist(ri, fip_cidr)
             self.mock_ip.del_veth.assert_called_once_with(
-                agent.get_fip_int_device_name(router['id']))
+                ri.fip_ns.get_int_device_name(router['id']))
             self.mock_ip_dev.route.delete_gateway.assert_called_once_with(
                 str(fip_to_rtr.ip), table=16)
             f.assert_called_once_with(fip_ns_name)
-        unsubscribe.assert_called_once_with(ri.router_id)
+        ri.fip_ns.unsubscribe.assert_called_once_with(ri.router_id)
 
     def test_get_service_plugin_list(self):
         service_plugins = [p_const.L3_ROUTER_NAT]
@@ -2080,74 +1970,41 @@ class TestBasicRouterOperations(base.BaseTestCase):
         self.assertRaises(messaging.MessagingTimeout, l3_agent.L3NATAgent,
                           HOSTNAME, self.conf)
 
-    def test__fip_ns_subscribe_is_first_true(self):
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        router_id = _uuid()
-        is_first = agent._fip_ns_subscribe(router_id)
-        self.assertTrue(is_first)
-        self.assertEqual(len(agent.fip_ns_subscribers), 1)
-
-    def test__fip_ns_subscribe_is_first_false(self):
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        router_id = _uuid()
-        router2_id = _uuid()
-        agent._fip_ns_subscribe(router_id)
-        is_first = agent._fip_ns_subscribe(router2_id)
-        self.assertFalse(is_first)
-        self.assertEqual(len(agent.fip_ns_subscribers), 2)
-
-    def test__fip_ns_unsubscribe_is_last_true(self):
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        router_id = _uuid()
-        agent.fip_ns_subscribers.add(router_id)
-        is_last = agent._fip_ns_unsubscribe(router_id)
-        self.assertTrue(is_last)
-        self.assertEqual(len(agent.fip_ns_subscribers), 0)
-
-    def test__fip_ns_unsubscribe_is_last_false(self):
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        router_id = _uuid()
-        router2_id = _uuid()
-        agent.fip_ns_subscribers.add(router_id)
-        agent.fip_ns_subscribers.add(router2_id)
-        is_last = agent._fip_ns_unsubscribe(router_id)
-        self.assertFalse(is_last)
-        self.assertEqual(len(agent.fip_ns_subscribers), 1)
-
     def test_external_gateway_removed_ext_gw_port_and_fip(self):
         self.conf.set_override('state_path', '/tmp')
         self.conf.set_override('router_delete_namespaces', True)
 
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         agent.conf.agent_mode = 'dvr'
-        agent.agent_gateway_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
-                                                   'subnet_id': _uuid()}],
-                                    'subnet': {'gateway_ip': '20.0.0.1'},
-                                    'id': _uuid(),
-                                    'network_id': _uuid(),
-                                    'mac_address': 'ca:fe:de:ad:be:ef',
-                                    'ip_cidr': '20.0.0.30/24'}
-        external_net_id = _uuid()
-        agent._fetch_external_net_id = mock.Mock(return_value=external_net_id)
-
         router = prepare_router_data(num_internal_ports=2)
         router['distributed'] = True
         router['gw_port_host'] = HOSTNAME
 
+        external_net_id = router['gw_port']['network_id']
         ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
+        ri.fip_ns = agent.get_fip_ns(external_net_id)
+        ri.fip_ns.agent_gateway_port = {
+            'fixed_ips': [{'ip_address': '20.0.0.30', 'subnet_id': _uuid()}],
+            'subnet': {'gateway_ip': '20.0.0.1'},
+            'id': _uuid(),
+            'network_id': external_net_id,
+            'mac_address': 'ca:fe:de:ad:be:ef',
+            'ip_cidr': '20.0.0.30/24'}
+        agent._fetch_external_net_id = mock.Mock(return_value=external_net_id)
+
         vm_floating_ip = '19.4.4.2'
         ri.floating_ips_dict[vm_floating_ip] = FIP_PRI
         ri.dist_fip_count = 1
         ri.ex_gw_port = ri.router['gw_port']
         del ri.router['gw_port']
-        ri.rtr_fip_subnet = agent.local_subnets.allocate(ri.router_id)
+        ri.rtr_fip_subnet = ri.fip_ns.local_subnets.allocate(ri.router_id)
         _, fip_to_rtr = ri.rtr_fip_subnet.get_pair()
         nat = ri.iptables_manager.ipv4['nat']
         nat.clear_rules_by_tag = mock.Mock()
         nat.add_rule = mock.Mock()
 
         self.mock_ip.get_devices.return_value = [
-            FakeDev(agent.get_fip_ext_device_name(_uuid()))]
+            FakeDev(ri.fip_ns.get_ext_device_name(_uuid()))]
         self.mock_ip_dev.addr.list.return_value = [
             {'cidr': vm_floating_ip + '/32'},
             {'cidr': '19.4.4.1/24'}]
@@ -2158,16 +2015,15 @@ class TestBasicRouterOperations(base.BaseTestCase):
             agent.get_external_device_name(ri.ex_gw_port['id']))
 
         self.mock_ip.del_veth.assert_called_once_with(
-            agent.get_fip_int_device_name(ri.router['id']))
+            ri.fip_ns.get_int_device_name(ri.router['id']))
         self.mock_ip_dev.route.delete_gateway.assert_called_once_with(
-            str(fip_to_rtr.ip), table=dvr.FIP_RT_TBL)
+            str(fip_to_rtr.ip), table=dvr_fip_ns.FIP_RT_TBL)
 
         self.assertEqual(ri.dist_fip_count, 0)
-        self.assertEqual(len(agent.fip_ns_subscribers), 0)
+        self.assertFalse(ri.fip_ns.has_subscribers())
         self.assertEqual(self.mock_driver.unplug.call_count, 1)
-        self.assertIsNone(agent.agent_gateway_port)
-        self.mock_ip.netns.delete.assert_called_once_with(
-            agent.get_fip_ns_name(external_net_id))
+        self.assertIsNone(ri.fip_ns.agent_gateway_port)
+        self.mock_ip.netns.delete.assert_called_once_with(ri.fip_ns.get_name())
         self.assertFalse(nat.add_rule.called)
         nat.clear_rules_by_tag.assert_called_once_with('floating_ip')