]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Modify L3 Agent for Distributed Routers
authorMichael Smith <michael.smith6@hp.com>
Tue, 22 Jul 2014 23:58:26 +0000 (16:58 -0700)
committerMichael Smith <michael.smith6@hp.com>
Wed, 23 Jul 2014 04:27:50 +0000 (21:27 -0700)
This patch is an enhancement to the existing L3 Agent.
This allows the L3 Agent to support distributed routers
by enhancing the router object to function across
multiple nodes.

Utilized two new types of namespaces:

- FIP to handle multiple VM fips and routers per node
- SNAT to handle centralized SNAT per router

Rules and tables are enhanced and added to support routing
across distributed routers without going to a centralized
router.

Finally, a new configuration param 'agent_mode' is introduced
and it controls what the L3 agent can do: the available values
are: 'legacy', 'dvr', 'dvr_snat' (more details inline).

The l3-scheduler uses the newly introduced agent_mode to
determine what L3 agent to select during the scheduling
process.

Partially-Implements: blueprint neutron-ovs-dvr

DocImpact

Change-Id: Icead821bb74372b15aac2e5cefe8ad7f08c037ab
Co-Authored-By: Rajeev Grover <rajeev.grover@hp.com>
etc/l3_agent.ini
neutron/agent/l3_agent.py
neutron/tests/unit/services/vpn/test_vpn_agent.py
neutron/tests/unit/test_l3_agent.py

index e6903988d4cf3974447a771d577d7c81517349a2..c8eafc841b8a612a46d09246f058be7236a74a1c 100644 (file)
 # Timeout for ovs-vsctl commands.
 # If the timeout expires, ovs commands will fail with ALARMCLOCK error.
 # ovs_vsctl_timeout = 10
+
+# The working mode for the agent. Allowed values are:
+# - legacy: this preserves the existing behavior where the L3 agent is
+#   deployed on a centralized networking node to provide L3 services
+#   like DNAT, and SNAT. Use this mode if you do not want to adopt DVR.
+# - dvr: this mode enables DVR functionality, and must be used for an L3
+#   agent that runs on a compute host.
+# - dvr_snat: this enables centralized SNAT support in conjunction with
+#   DVR. This mode must be used for an L3 agent running on a centralized
+#   node (or in single-host deployments, e.g. devstack).
+# agent_mode = legacy
index d0a59a92b76b45bc11e6e1896936fa7068a47044..9d7ec1219515fc501840afcf76db30ca24689b0a 100644 (file)
@@ -28,7 +28,6 @@ from neutron.agent.linux import external_process
 from neutron.agent.linux import interface
 from neutron.agent.linux import ip_lib
 from neutron.agent.linux import iptables_manager
-from neutron.agent.linux import ovs_lib  # noqa
 from neutron.agent.linux import ra
 from neutron.agent import rpc as agent_rpc
 from neutron.common import config as common_config
@@ -53,6 +52,18 @@ LOG = logging.getLogger(__name__)
 NS_PREFIX = 'qrouter-'
 INTERNAL_DEV_PREFIX = 'qr-'
 EXTERNAL_DEV_PREFIX = 'qg-'
+SNAT_INT_DEV_PREFIX = 'sg-'
+FIP_NS_PREFIX = 'fip-'
+SNAT_NS_PREFIX = 'snat-'
+FIP_2_ROUTER_DEV_PREFIX = 'fpr-'
+ROUTER_2_FIP_DEV_PREFIX = 'rfp-'
+FIP_EXT_DEV_PREFIX = 'fg-'
+FIP_LL_PREFIX = '169.254.30.'
+# Route Table index for FIPs
+FIP_RT_TBL = 16
+# Rule priority range for FIPs
+FIP_PR_START = 32768
+FIP_PR_END = FIP_PR_START + 40000
 RPC_LOOP_INTERVAL = 1
 FLOATING_IP_CIDR_SUFFIX = '/32'
 # Lower value is higher priority
@@ -67,6 +78,10 @@ class L3PluginApi(n_rpc.RpcProxy):
     API version history:
         1.0 - Initial version.
         1.1 - Floating IP operational status updates
+        1.2 - DVR support: new L3 plugin methods added.
+              - get_ports_by_subnet
+              - get_agent_gateway_port
+              Needed by the agent when operating in DVR/DVR_SNAT mode
 
     """
 
@@ -105,6 +120,22 @@ class L3PluginApi(n_rpc.RpcProxy):
                          topic=self.topic,
                          version='1.1')
 
+    def get_ports_by_subnet(self, context, subnet_id):
+        """Retrieve ports by subnet id."""
+        return self.call(context,
+                         self.make_msg('get_ports_by_subnet', host=self.host,
+                                       subnet_id=subnet_id),
+                         topic=self.topic,
+                         version='1.2')
+
+    def get_agent_gateway_port(self, context, fip_net):
+        """Get or create an agent_gateway_port."""
+        return self.call(context,
+                         self.make_msg('get_agent_gateway_port',
+                                       network_id=fip_net, host=self.host),
+                         topic=self.topic,
+                         version='1.2')
+
 
 class RouterInfo(object):
 
@@ -114,7 +145,9 @@ class RouterInfo(object):
         self._snat_enabled = None
         self._snat_action = None
         self.internal_ports = []
+        self.snat_ports = []
         self.floating_ips = set()
+        self.floating_ips_dict = {}
         self.root_helper = root_helper
         self.use_namespaces = use_namespaces
         # Invoke the setter for establishing initial SNAT action
@@ -125,6 +158,12 @@ class RouterInfo(object):
             #FIXME(danwent): use_ipv6=True,
             namespace=self.ns_name)
         self.routes = []
+        # DVR Data
+        # Linklocal router to floating IP addr
+        self.rtr_2_fip = None
+        # Linklocal floating to router IP addr
+        self.fip_2_rtr = None
+        self.dist_fip_count = 0
 
     @property
     def router(self):
@@ -301,10 +340,27 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
             It was previously a list of routers in dict format.
             It is now a list of router IDs only.
             Per rpc versioning rules,  it is backwards compatible.
+        1.2 - DVR support: new L3 agent methods added.
+              - add_arp_entry
+              - del_arp_entry
+              Needed by the L3 service when dealing with DVR
     """
-    RPC_API_VERSION = '1.1'
+    RPC_API_VERSION = '1.2'
 
     OPTS = [
+        cfg.StrOpt('agent_mode', default='legacy',
+                   help=_("The working mode for the agent. Allowed modes are: "
+                          "'legacy' - this preserves the existing behavior "
+                          "where the L3 agent is deployed on a centralized "
+                          "networking node to provide L3 services like DNAT, "
+                          "and SNAT. Use this mode if you do not want to "
+                          "adopt DVR. 'dvr' - this mode enables DVR "
+                          "functionality and must be used for an L3 agent "
+                          "that runs on a compute host. 'dvr_snat' - this "
+                          "enables centralized SNAT support in conjunction "
+                          "with DVR.  This mode must be used for an L3 agent "
+                          "running on a centralized node (or in single-host "
+                          "deployments, e.g. devstack)")),
         cfg.StrOpt('external_network_bridge', default='br-ex',
                    help=_("Name of bridge used for external network "
                           "traffic.")),
@@ -366,6 +422,12 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
 
         self._clean_stale_namespaces = self.conf.use_namespaces
 
+        # dvr data
+        self.agent_gateway_port = None
+        self.agent_fip_count = 0
+        self.local_ips = set(range(2, 251))
+        self.fip_priorities = set(range(FIP_PR_START, FIP_PR_END))
+
         self._queue = RouterProcessingQueue()
         super(L3NATAgent, self).__init__(conf=self.conf)
 
@@ -425,41 +487,89 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
         one attempt will be made to delete them.
         """
         for ns in router_namespaces:
-            if self.conf.enable_metadata_proxy:
-                self._destroy_metadata_proxy(ns[len(NS_PREFIX):], ns)
-
             ra.disable_ipv6_ra(ns[len(NS_PREFIX):], ns, self.root_helper)
             try:
-                self._destroy_router_namespace(ns)
+                self._destroy_namespace(ns)
             except RuntimeError:
                 LOG.exception(_('Failed to destroy stale router namespace '
                                 '%s'), ns)
         self._clean_stale_namespaces = False
 
-    def _destroy_router_namespace(self, namespace):
-        ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=namespace)
+    def _destroy_namespace(self, ns):
+        if ns.startswith(NS_PREFIX):
+            if self.conf.enable_metadata_proxy:
+                self._destroy_metadata_proxy(ns[len(NS_PREFIX):], ns)
+            self._destroy_router_namespace(ns)
+        elif ns.startswith(FIP_NS_PREFIX):
+            self._destroy_fip_namespace(ns)
+        elif ns.startswith(SNAT_NS_PREFIX):
+            self._destroy_snat_namespace(ns)
+
+    def _delete_namespace(self, ns_ip, ns):
+        try:
+            ns_ip.netns.delete(ns)
+        except RuntimeError:
+            msg = _('Failed trying to delete namespace: %s') % ns
+            LOG.exception(msg)
+
+    def _destroy_snat_namespace(self, ns):
+        ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=ns)
+        # delete internal interfaces
+        for d in ns_ip.get_devices(exclude_loopback=True):
+            if d.name.startswith(SNAT_INT_DEV_PREFIX):
+                LOG.debug('Unplugging DVR device %s', d.name)
+                self.driver.unplug(d.name, namespace=ns,
+                                   prefix=SNAT_INT_DEV_PREFIX)
+
+        # TODO(mrsmith): delete ext-gw-port
+        LOG.debug('DVR: destroy snat ns: %s', ns)
+        if self.conf.router_delete_namespaces:
+            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
+                # TODO(mrsmith): remove IR interfaces (IP pool?)
+                pass
+            elif d.name.startswith(FIP_EXT_DEV_PREFIX):
+                # single port from FIP NS to br-ext
+                # TODO(mrsmith): remove br-ext interface
+                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
+
+    def _destroy_router_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(INTERNAL_DEV_PREFIX):
                 # device is on default bridge
-                self.driver.unplug(d.name, namespace=namespace,
+                self.driver.unplug(d.name, namespace=ns,
                                    prefix=INTERNAL_DEV_PREFIX)
             elif d.name.startswith(EXTERNAL_DEV_PREFIX):
                 self.driver.unplug(d.name,
                                    bridge=self.conf.external_network_bridge,
-                                   namespace=namespace,
+                                   namespace=ns,
                                    prefix=EXTERNAL_DEV_PREFIX)
 
         if self.conf.router_delete_namespaces:
-            try:
-                ns_ip.netns.delete(namespace)
-            except RuntimeError:
-                msg = _('Failed trying to delete namespace: %s')
-                LOG.exception(msg % namespace)
+            self._delete_namespace(ns_ip, ns)
+
+    def _create_namespace(self, name):
+        ip_wrapper_root = ip_lib.IPWrapper(self.root_helper)
+        ip_wrapper = ip_wrapper_root.ensure_namespace(name)
+        ip_wrapper.netns.execute(['sysctl', '-w', 'net.ipv4.ip_forward=1'])
 
     def _create_router_namespace(self, ri):
-            ip_wrapper_root = ip_lib.IPWrapper(self.root_helper)
-            ip_wrapper = ip_wrapper_root.ensure_namespace(ri.ns_name)
-            ip_wrapper.netns.execute(['sysctl', '-w', 'net.ipv4.ip_forward=1'])
+        self._create_namespace(ri.ns_name)
 
     def _fetch_external_net_id(self, force=False):
         """Find UUID of single external network for this agent."""
@@ -553,6 +663,24 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
             ns_name)
         pm.disable()
 
+    def _set_subnet_arp_info(self, ri, port):
+        """Set ARP info retrieved from Plugin for existing ports."""
+        if 'id' not in port['subnet'] or not ri.router['distributed']:
+            return
+        subnet_id = port['subnet']['id']
+        subnet_ports = (
+            self.plugin_rpc.get_ports_by_subnet(self.context,
+                                                subnet_id))
+
+        for p in subnet_ports:
+            if (p['device_owner'] not in (
+                l3_constants.DEVICE_OWNER_ROUTER_INTF,
+                l3_constants.DEVICE_OWNER_DVR_INTERFACE)):
+                for fixed_ip in p['fixed_ips']:
+                    self._update_arp_entry(ri, fixed_ip['ip_address'],
+                                           p['mac_address'],
+                                           subnet_id, 'add')
+
     def _set_subnet_info(self, port):
         ips = port['fixed_ips']
         if not ips:
@@ -570,9 +698,13 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
         return [ip_dev.name for ip_dev in ip_devs]
 
     def process_router(self, ri):
+        # TODO(mrsmith) - we shouldn't need to check here
+        if 'distributed' not in ri.router:
+            ri.router['distributed'] = False
         ri.iptables_manager.defer_apply_on()
         ex_gw_port = self._get_ex_gw_port(ri)
         internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
+        snat_ports = ri.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, [])
         existing_port_ids = set([p['id'] for p in ri.internal_ports])
         current_port_ids = set([p['id'] for p in internal_ports
                                 if p['admin_state_up']])
@@ -586,15 +718,15 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
         old_ipv6_port = False
         for p in new_ports:
             self._set_subnet_info(p)
-            self.internal_network_added(ri, p['network_id'], p['id'],
-                                        p['ip_cidr'], p['mac_address'])
+            self.internal_network_added(ri, p)
             ri.internal_ports.append(p)
+            self._set_subnet_arp_info(ri, p)
             if (not new_ipv6_port and
                     netaddr.IPNetwork(p['subnet']['cidr']).version == 6):
                 new_ipv6_port = True
 
         for p in old_ports:
-            self.internal_network_removed(ri, p['id'], p['ip_cidr'])
+            self.internal_network_removed(ri, p)
             ri.internal_ports.remove(p)
             if (not old_ipv6_port and
                     netaddr.IPNetwork(p['subnet']['cidr']).version == 6):
@@ -653,8 +785,10 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
         # Process static routes for router
         self.routes_updated(ri)
         # Process SNAT rules for external gateway
-        ri.perform_snat_action(self._handle_router_snat_rules,
-                               internal_cidrs, interface_name)
+        if (not ri.router['distributed'] or
+            ex_gw_port and ri.router['gw_port_host'] == self.host):
+            ri.perform_snat_action(self._handle_router_snat_rules,
+                                   internal_cidrs, interface_name)
 
         # Process SNAT/DNAT rules for floating IPs
         fip_statuses = {}
@@ -684,6 +818,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
 
         # Update ex_gw_port and enable_snat on the router info cache
         ri.ex_gw_port = ex_gw_port
+        ri.snat_ports = snat_ports
         ri.enable_snat = ri.router.get('enable_snat')
 
     def _handle_router_snat_rules(self, ri, ex_gw_port, internal_cidrs,
@@ -692,13 +827,19 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
         # This is safe because if use_namespaces is set as False
         # then the agent can only configure one router, otherwise
         # each router's SNAT rules will be in their own namespace
-        ri.iptables_manager.ipv4['nat'].empty_chain('POSTROUTING')
-        ri.iptables_manager.ipv4['nat'].empty_chain('snat')
+        if ri.router['distributed']:
+            iptables_manager = ri.snat_iptables_manager
+        else:
+            iptables_manager = ri.iptables_manager
 
-        # Add back the jump to float-snat
-        ri.iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat')
+        iptables_manager.ipv4['nat'].empty_chain('POSTROUTING')
+        iptables_manager.ipv4['nat'].empty_chain('snat')
+
+        if not ri.router['distributed']:
+            # Add back the jump to float-snat
+            iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat')
 
-        # And add them back if the action if add_rules
+        # And add them back if the action is add_rules
         if action == 'add_rules' and ex_gw_port:
             # ex_gw_port should not be None in this case
             # NAT rules are added only if ex_gw_port has an IPv4 address
@@ -709,8 +850,31 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
                                                             internal_cidrs,
                                                             interface_name)
                     for rule in rules:
-                        ri.iptables_manager.ipv4['nat'].add_rule(*rule)
+                        iptables_manager.ipv4['nat'].add_rule(*rule)
                     break
+        iptables_manager.apply()
+
+    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 process_router_floating_ip_nat_rules(self, ri):
@@ -721,8 +885,9 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
         # Clear out all iptables rules for floating ips
         ri.iptables_manager.ipv4['nat'].clear_rules_by_tag('floating_ip')
 
+        floating_ips = self.get_floating_ips(ri)
         # Loop once to ensure that floating ips are configured.
-        for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []):
+        for fip in floating_ips:
             # Rebuild iptables rules for the floating ip.
             fixed = fip['fixed_ip_address']
             fip_ip = fip['floating_ip_address']
@@ -739,14 +904,33 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
         those that should not longer be configured.
         """
         fip_statuses = {}
-        interface_name = self.get_external_device_name(ex_gw_port['id'])
+
+        floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
+        if ri.router['distributed']:
+            # filter out only FIPs for this host/agent
+            floating_ips = [i for i in floating_ips if i['host'] == self.host]
+            if floating_ips and self.agent_gateway_port is None:
+                self._create_agent_gateway_port(ri, floating_ips[0]
+                                                ['floating_network_id'])
+
+            if self.agent_gateway_port:
+                if floating_ips and ri.dist_fip_count == 0:
+                    self.create_rtr_2_fip_link(ri, floating_ips[0]
+                                               ['floating_network_id'])
+                interface_name = self.get_rtr_int_device_name(ri.router_id)
+            else:
+                # there are no fips or agent port, no work to do
+                return fip_statuses
+        else:
+            interface_name = self.get_external_device_name(ex_gw_port['id'])
+
         device = ip_lib.IPDevice(interface_name, self.root_helper,
                                  namespace=ri.ns_name)
         existing_cidrs = set([addr['cidr'] for addr in device.addr.list()])
         new_cidrs = set()
 
         # Loop once to ensure that floating ips are configured.
-        for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []):
+        for fip in floating_ips:
             fip_ip = fip['floating_ip_address']
             ip_cidr = str(fip_ip) + FLOATING_IP_CIDR_SUFFIX
 
@@ -765,10 +949,15 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
                     LOG.warn(_("Unable to configure IP address for "
                                "floating IP: %s"), fip['id'])
                     continue
-                # As GARP is processed in a distinct thread the call below
-                # won't raise an exception to be handled.
-                self._send_gratuitous_arp_packet(
-                    ri, interface_name, fip_ip)
+                if ri.router['distributed']:
+                    # Special Handling for DVR - update FIP namespace
+                    # and ri.namespace to handle DVR based FIP
+                    self.floating_ip_added_dist(ri, fip)
+                else:
+                    # As GARP is processed in a distinct thread the call below
+                    # won't raise an exception to be handled.
+                    self._send_gratuitous_arp_packet(
+                        ri.ns_name, interface_name, fip_ip)
             fip_statuses[fip['id']] = (
                 l3_constants.FLOATINGIP_STATUS_ACTIVE)
 
@@ -777,26 +966,48 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
             if ip_cidr.endswith(FLOATING_IP_CIDR_SUFFIX):
                 net = netaddr.IPNetwork(ip_cidr)
                 device.addr.delete(net.version, ip_cidr)
+                if ri.router['distributed']:
+                    self.floating_ip_removed_dist(ri, ip_cidr)
         return fip_statuses
 
     def _get_ex_gw_port(self, ri):
         return ri.router.get('gw_port')
 
-    def _arping(self, ri, interface_name, ip_address):
+    def _arping(self, ns_name, interface_name, ip_address, distributed=False):
+        if distributed:
+            device = ip_lib.IPDevice(interface_name, self.root_helper,
+                                     namespace=ns_name)
+            ip_cidr = str(ip_address) + FLOATING_IP_CIDR_SUFFIX
+            net = netaddr.IPNetwork(ip_cidr)
+            device.addr.add(net.version, ip_cidr, str(net.broadcast))
+
         arping_cmd = ['arping', '-A',
                       '-I', interface_name,
                       '-c', self.conf.send_arp_for_ha,
                       ip_address]
         try:
             ip_wrapper = ip_lib.IPWrapper(self.root_helper,
-                                          namespace=ri.ns_name)
+                                          namespace=ns_name)
             ip_wrapper.netns.execute(arping_cmd, check_exit_code=True)
         except Exception as e:
             LOG.error(_("Failed sending gratuitous ARP: %s"), str(e))
+        if distributed:
+            device.addr.delete(net.version, ip_cidr)
 
-    def _send_gratuitous_arp_packet(self, ri, interface_name, ip_address):
+    def _send_gratuitous_arp_packet(self, ns_name, interface_name, ip_address,
+                                    distributed=False):
         if self.conf.send_arp_for_ha > 0:
-            eventlet.spawn_n(self._arping, ri, interface_name, ip_address)
+            eventlet.spawn_n(self._arping, ns_name, interface_name, ip_address,
+                             distributed)
+
+    def get_internal_port(self, ri, subnet_id):
+        """Return internal router port based on subnet_id."""
+        router_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
+        for port in router_ports:
+            fips = port['fixed_ips']
+            for f in fips:
+                if f['subnet_id'] == subnet_id:
+                    return port
 
     def get_internal_device_name(self, port_id):
         return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
@@ -804,38 +1015,184 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
     def get_external_device_name(self, port_id):
         return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
 
+    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)
+
+    def get_snat_interfaces(self, ri):
+        return ri.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, [])
+
+    def get_floating_ips(self, ri):
+        """Filter Floating IPs to be hosted on this agent."""
+        floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
+        if ri.router['distributed']:
+            floating_ips = [i for i in floating_ips if i['host'] == self.host]
+        return floating_ips
+
+    def _map_internal_interfaces(self, ri, int_port, snat_ports):
+        """Return the SNAT port for the given internal interface port."""
+        fixed_ip = int_port['fixed_ips'][0]
+        subnet_id = fixed_ip['subnet_id']
+        match_port = [p for p in snat_ports if
+                      p['fixed_ips'][0]['subnet_id'] == subnet_id]
+        if match_port:
+            return match_port[0]
+        else:
+            LOG.error(_('DVR: no map match_port found!'))
+
+    def _create_dvr_gateway(self, ri, ex_gw_port, gw_interface_name,
+                            internal_cidrs, snat_ports):
+        """Create SNAT namespace."""
+        snat_ns_name = self.get_snat_ns_name(ri.router['id'])
+        self._create_namespace(snat_ns_name)
+        # connect snat_ports to br_int from SNAT namespace
+        for port in snat_ports:
+            # create interface_name
+            self._set_subnet_info(port)
+            interface_name = self.get_snat_int_device_name(port['id'])
+            self._internal_network_added(snat_ns_name, port['network_id'],
+                                         port['id'], port['ip_cidr'],
+                                         port['mac_address'], interface_name,
+                                         SNAT_INT_DEV_PREFIX)
+        self._external_gateway_added(ri, ex_gw_port, gw_interface_name,
+                                     internal_cidrs, snat_ns_name,
+                                     preserve_ips=[])
+        ri.snat_iptables_manager = (
+            iptables_manager.IptablesManager(
+                root_helper=self.root_helper, namespace=snat_ns_name
+            )
+        )
+
     def external_gateway_added(self, ri, ex_gw_port,
                                interface_name, internal_cidrs):
-
-        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=ri.ns_name,
-                         prefix=EXTERNAL_DEV_PREFIX)
+        if ri.router['distributed']:
+            ip_wrapr = ip_lib.IPWrapper(self.root_helper, namespace=ri.ns_name)
+            ip_wrapr.netns.execute(['sysctl', '-w',
+                                   'net.ipv4.conf.all.send_redirects=0'])
+            snat_ports = self.get_snat_interfaces(ri)
+            for p in ri.internal_ports:
+                gateway = self._map_internal_interfaces(ri, p, snat_ports)
+                id_name = self.get_internal_device_name(p['id'])
+                if gateway:
+                    self._snat_redirect_add(ri, gateway['fixed_ips'][0]
+                                            ['ip_address'], p, id_name)
+
+            if self.conf.agent_mode == 'dvr_snat' and (
+                    ri.router['gw_port_host'] == self.host):
+                if snat_ports:
+                    self._create_dvr_gateway(ri, ex_gw_port,
+                                             interface_name,
+                                             internal_cidrs, snat_ports)
+            for port in snat_ports:
+                for ip in port['fixed_ips']:
+                    self._update_arp_entry(ri, ip['ip_address'],
+                                           port['mac_address'],
+                                           ip['subnet_id'], 'add')
+            return
 
         # Compute a list of addresses this router is supposed to have.
         # This avoids unnecessarily removing those addresses and
         # causing a momentarily network outage.
-        floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
+        floating_ips = self.get_floating_ips(ri)
         preserve_ips = [ip['floating_ip_address'] + FLOATING_IP_CIDR_SUFFIX
                         for ip in floating_ips]
 
+        self._external_gateway_added(ri, ex_gw_port, interface_name,
+                                     internal_cidrs, ri.ns_name,
+                                     preserve_ips)
+
+    def _external_gateway_added(self, ri, ex_gw_port, interface_name,
+                                internal_cidrs, ns_name, preserve_ips):
+        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=EXTERNAL_DEV_PREFIX)
+
         self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
-                            namespace=ri.ns_name,
+                            namespace=ns_name,
                             gateway=ex_gw_port['subnet'].get('gateway_ip'),
                             extra_subnets=ex_gw_port.get('extra_subnets', []),
                             preserve_ips=preserve_ips)
         ip_address = ex_gw_port['ip_cidr'].split('/')[0]
-        self._send_gratuitous_arp_packet(ri, interface_name, ip_address)
+        self._send_gratuitous_arp_packet(ns_name,
+                                         interface_name, ip_address)
+
+    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]
+        self._send_gratuitous_arp_packet(ns_name, interface_name, ip_address)
+
+        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 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 external_gateway_removed(self, ri, ex_gw_port,
                                  interface_name, internal_cidrs):
+        if ri.router['distributed']:
+            for p in ri.internal_ports:
+                internal_interface = self.get_internal_device_name(p['id'])
+                self._snat_redirect_remove(ri, p, internal_interface)
+
+            if self.conf.agent_mode == 'dvr_snat' and (
+                ex_gw_port['binding:host_id'] == self.host):
+                ns_name = self.get_snat_ns_name(ri.router['id'])
+            else:
+                # not hosting agent - no work to do
+                LOG.debug('DVR: CSNAT not hosted: %s', ex_gw_port)
+                return
+        else:
+            ns_name = ri.ns_name
 
         self.driver.unplug(interface_name,
                            bridge=self.conf.external_network_bridge,
-                           namespace=ri.ns_name,
+                           namespace=ns_name,
                            prefix=EXTERNAL_DEV_PREFIX)
+        if ri.router['distributed']:
+            self._destroy_snat_namespace(ns_name)
 
     def metadata_filter_rules(self):
         rules = []
@@ -863,23 +1220,100 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
             rules.extend(self.internal_network_nat_rules(ex_gw_ip, cidr))
         return rules
 
-    def internal_network_added(self, ri, network_id, port_id,
-                               internal_cidr, mac_address):
-        interface_name = self.get_internal_device_name(port_id)
+    def _snat_redirect_add(self, ri, gateway, sn_port, sn_int):
+        """Adds rules and routes for SNAT redirection."""
+        try:
+            snat_idx = netaddr.IPNetwork(sn_port['ip_cidr']).value
+            ns_ipr = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
+            ns_ipd = ip_lib.IPDevice(sn_int, self.root_helper,
+                                     namespace=ri.ns_name)
+            ns_ipd.route.add_gateway(gateway, table=snat_idx)
+            ns_ipr.add_rule_from(sn_port['ip_cidr'], snat_idx, snat_idx)
+            ns_ipr.netns.execute(['sysctl', '-w', 'net.ipv4.conf.%s.'
+                                 'send_redirects=0' % sn_int])
+        except Exception:
+            LOG.exception(_('DVR: error adding redirection logic'))
+
+    def _snat_redirect_remove(self, ri, sn_port, sn_int):
+        """Removes rules and routes for SNAT redirection."""
+        try:
+            snat_idx = netaddr.IPNetwork(sn_port['ip_cidr']).value
+            ns_ipr = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
+            ns_ipd = ip_lib.IPDevice(sn_int, self.root_helper,
+                                     namespace=ri.ns_name)
+            ns_ipd.route.delete_gateway(table=snat_idx)
+            ns_ipr.delete_rule_priority(snat_idx)
+        except Exception:
+            LOG.exception(_('DVR: removed snat failed'))
+
+    def _internal_network_added(self, ns_name, network_id, port_id,
+                                internal_cidr, mac_address,
+                                interface_name, prefix):
         if not ip_lib.device_exists(interface_name,
                                     root_helper=self.root_helper,
-                                    namespace=ri.ns_name):
+                                    namespace=ns_name):
             self.driver.plug(network_id, port_id, interface_name, mac_address,
-                             namespace=ri.ns_name,
-                             prefix=INTERNAL_DEV_PREFIX)
+                             namespace=ns_name,
+                             prefix=prefix)
 
         self.driver.init_l3(interface_name, [internal_cidr],
-                            namespace=ri.ns_name)
+                            namespace=ns_name)
         ip_address = internal_cidr.split('/')[0]
-        self._send_gratuitous_arp_packet(ri, interface_name, ip_address)
+        self._send_gratuitous_arp_packet(ns_name, interface_name, ip_address)
+
+    def internal_network_added(self, ri, port):
+        network_id = port['network_id']
+        port_id = port['id']
+        internal_cidr = port['ip_cidr']
+        mac_address = port['mac_address']
+
+        interface_name = self.get_internal_device_name(port_id)
+
+        self._internal_network_added(ri.ns_name, network_id, port_id,
+                                     internal_cidr, mac_address,
+                                     interface_name, INTERNAL_DEV_PREFIX)
 
-    def internal_network_removed(self, ri, port_id, internal_cidr):
+        ex_gw_port = self._get_ex_gw_port(ri)
+        if ri.router['distributed'] and ex_gw_port:
+            snat_ports = self.get_snat_interfaces(ri)
+            snat_ip = self._map_internal_interfaces(ri, port, snat_ports)
+            if snat_ip:
+                self._snat_redirect_add(ri, snat_ip['fixed_ips'][0]
+                                        ['ip_address'], port, interface_name)
+            if self.conf.agent_mode == 'dvr_snat' and (
+                    ri.router['gw_port_host'] == self.host):
+                ns_name = self.get_snat_ns_name(ri.router['id'])
+                for port in snat_ports:
+                    self._set_subnet_info(port)
+                    interface_name = self.get_snat_int_device_name(port['id'])
+                    self._internal_network_added(ns_name, port['network_id'],
+                                                 port['id'], internal_cidr,
+                                                 port['mac_address'],
+                                                 interface_name,
+                                                 SNAT_INT_DEV_PREFIX)
+
+    def internal_network_removed(self, ri, port):
+        port_id = port['id']
         interface_name = self.get_internal_device_name(port_id)
+        if ri.router['distributed'] and ri.ex_gw_port:
+            # DVR handling code for SNAT
+            self._snat_redirect_remove(ri, port, interface_name)
+            if self.conf.agent_mode == 'dvr_snat' and (
+                ri.ex_gw_port['binding:host_id'] == self.host):
+                snat_port = self._map_internal_interfaces(ri, port,
+                                                          ri.snat_ports)
+                if snat_port:
+                    snat_interface = (
+                        self.get_snat_int_device_name(snat_port['id'])
+                    )
+                    ns_name = self.get_snat_ns_name(ri.router['id'])
+                    prefix = SNAT_INT_DEV_PREFIX
+                    if ip_lib.device_exists(snat_interface,
+                                            root_helper=self.root_helper,
+                                            namespace=ns_name):
+                        self.driver.unplug(snat_interface, namespace=ns_name,
+                                           prefix=prefix)
+
         if ip_lib.device_exists(interface_name,
                                 root_helper=self.root_helper,
                                 namespace=ri.ns_name):
@@ -891,6 +1325,118 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
                  (internal_cidr, ex_gw_ip))]
         return rules
 
+    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(_('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)
+        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_2_fip is None:
+            ri.rtr_2_fip = FIP_LL_PREFIX + str(self.local_ips.pop())
+        if ri.fip_2_rtr is None:
+            ri.fip_2_rtr = FIP_LL_PREFIX + str(self.local_ips.pop())
+        ip_wrapper = ip_lib.IPWrapper(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(ri.rtr_2_fip + '/31',
+                                         rtr_2_fip_name, ri.ns_name)
+        self.internal_ns_interface_added(ri.fip_2_rtr + '/31',
+                                         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(ri.fip_2_rtr, table=FIP_RT_TBL)
+        #setup the NAT rules and chains
+        self._handle_router_fip_nat_rules(ri, rtr_2_fip_name, 'add_rules')
+
+    def floating_ip_added_dist(self, ri, fip):
+        """Add floating IP to FIP namespace."""
+        floating_ip = fip['floating_ip_address']
+        fixed_ip = fip['fixed_ip_address']
+        rule_pr = self.fip_priorities.pop()
+        ri.floating_ips_dict[floating_ip] = rule_pr
+        fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id)
+        ip_rule = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
+        ip_rule.add_rule_from(fixed_ip, FIP_RT_TBL, rule_pr)
+
+        #Add routing rule in fip namespace
+        fip_cidr = str(floating_ip) + FLOATING_IP_CIDR_SUFFIX
+        fip_ns_name = self.get_fip_ns_name(str(fip['floating_network_id']))
+        device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper,
+                                 namespace=fip_ns_name)
+        device.route.add_route(fip_cidr, ri.rtr_2_fip)
+        interface_name = (
+            self.get_fip_ext_device_name(self.agent_gateway_port['id']))
+        self._send_gratuitous_arp_packet(fip_ns_name,
+                                         interface_name, floating_ip,
+                                         distributed=True)
+        # update internal structures
+        self.agent_fip_count = self.agent_fip_count + 1
+        ri.dist_fip_count = ri.dist_fip_count + 1
+
+    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)
+        fip_ns_name = self.get_fip_ns_name(str(self._fetch_external_net_id()))
+        ip_rule_rtr = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
+        if floating_ip in ri.floating_ips_dict:
+            rule_pr = ri.floating_ips_dict[floating_ip]
+            #TODO(rajeev): Handle else case - exception/log?
+        else:
+            rule_pr = None
+
+        ip_rule_rtr.delete_rule_priority(rule_pr)
+        self.fip_priorities.add(rule_pr)
+        device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper,
+                                 namespace=fip_ns_name)
+
+        device.route.delete_route(fip_cidr, ri.rtr_2_fip)
+        # check if this is the last FIP for this router
+        ri.dist_fip_count = ri.dist_fip_count - 1
+        if ri.dist_fip_count == 0:
+            #remove default route entry
+            device = ip_lib.IPDevice(rtr_2_fip_name, self.root_helper,
+                                     namespace=ri.ns_name)
+            device.route.delete_gateway(ri.fip_2_rtr, table=FIP_RT_TBL)
+            self.local_ips.add(ri.rtr_2_fip.rsplit('.', 1)[1])
+            ri.rtr_2_fip = None
+            self.local_ips.add(ri.fip_2_rtr.rsplit('.', 1)[1])
+            ri.fip_2_rtr = None
+            # TODO(mrsmith): remove interface
+        # clean up fip-namespace if this is the last FIP
+        self.agent_fip_count = self.agent_fip_count - 1
+        if self.agent_fip_count == 0:
+            self._destroy_fip_namespace(fip_ns_name)
+
     def floating_forward_rules(self, floating_ip, fixed_ip):
         return [('PREROUTING', '-d %s -j DNAT --to %s' %
                  (floating_ip, fixed_ip)),
@@ -905,6 +1451,46 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
         update = RouterUpdate(router_id, PRIORITY_RPC, action=DELETE_ROUTER)
         self._queue.add(update)
 
+    def _update_arp_entry(self, ri, ip, mac, subnet_id, operation):
+        """Add or delete arp entry into router namespace."""
+        port = self.get_internal_port(ri, subnet_id)
+        if 'id' in port:
+            ip_cidr = str(ip) + '/32'
+            try:
+                # TODO(mrsmith): optimize the calls below for bulk calls
+                net = netaddr.IPNetwork(ip_cidr)
+                interface_name = self.get_internal_device_name(port['id'])
+                device = ip_lib.IPDevice(interface_name, self.root_helper,
+                                         namespace=ri.ns_name)
+                if operation == 'add':
+                    device.neigh.add(net.version, ip, mac)
+                elif operation == 'delete':
+                    device.neigh.delete(net.version, ip, mac)
+            except Exception:
+                LOG.exception(_("DVR: Failed updating arp entry"))
+                self.fullsync = True
+
+    def add_arp_entry(self, context, payload):
+        """Add arp entry into router namespace.  Called from RPC."""
+        arp_table = payload['arp_table']
+        router_id = payload['router_id']
+        ip = arp_table['ip_address']
+        mac = arp_table['mac_address']
+        subnet_id = arp_table['subnet_id']
+        ri = self.router_info.get(router_id)
+        self._update_arp_entry(ri, ip, mac, subnet_id, 'add')
+
+    def del_arp_entry(self, context, payload):
+        """Delete arp entry from router namespace.  Called from RPC."""
+        arp_table = payload['arp_table']
+        router_id = payload['router_id']
+        ip = arp_table['ip_address']
+        mac = arp_table['mac_address']
+        subnet_id = arp_table['subnet_id']
+        ri = self.router_info.get(router_id)
+        if ri:
+            self._update_arp_entry(ri, ip, mac, subnet_id, 'delete')
+
     def routers_updated(self, context, routers):
         """Deal with routers modification and creation RPC message."""
         LOG.debug(_('Got routers updated notification :%s'), routers)
@@ -1115,6 +1701,7 @@ class L3NATAgentWithStateReport(L3NATAgent):
             'host': host,
             'topic': topics.L3_AGENT,
             'configurations': {
+                'agent_mode': self.conf.agent_mode,
                 'use_namespaces': self.conf.use_namespaces,
                 'router_id': self.conf.router_id,
                 'handle_internal_only_routers':
index 09f5594714ae46c964023356174fefeb4ecce87c..b998c948d328081ff5e7122a205fb0ab596d6ad1 100644 (file)
@@ -92,7 +92,7 @@ class TestVPNAgent(base.BaseTestCase):
     def test_get_namespace(self):
         router_id = _uuid()
         ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
-                                 self.conf.use_namespaces, None)
+                                 self.conf.use_namespaces, {})
         self.agent.router_info = {router_id: ri}
         namespace = self.agent.get_namespace(router_id)
         self.assertTrue(namespace.endswith(router_id))
@@ -101,7 +101,7 @@ class TestVPNAgent(base.BaseTestCase):
     def test_add_nat_rule(self):
         router_id = _uuid()
         ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
-                                 self.conf.use_namespaces, None)
+                                 self.conf.use_namespaces, {})
         iptables = mock.Mock()
         ri.iptables_manager.ipv4['nat'] = iptables
         self.agent.router_info = {router_id: ri}
@@ -121,7 +121,7 @@ class TestVPNAgent(base.BaseTestCase):
     def test_remove_rule(self):
         router_id = _uuid()
         ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
-                                 self.conf.use_namespaces, None)
+                                 self.conf.use_namespaces, {})
         iptables = mock.Mock()
         ri.iptables_manager.ipv4['nat'] = iptables
         self.agent.router_info = {router_id: ri}
@@ -140,7 +140,7 @@ class TestVPNAgent(base.BaseTestCase):
     def test_iptables_apply(self):
         router_id = _uuid()
         ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
-                                 self.conf.use_namespaces, None)
+                                 self.conf.use_namespaces, {})
         iptables = mock.Mock()
         ri.iptables_manager = iptables
         self.agent.router_info = {router_id: ri}
@@ -168,12 +168,13 @@ class TestVPNAgent(base.BaseTestCase):
             'neutron.agent.linux.iptables_manager.IptablesManager').start()
         router_id = _uuid()
         ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
-                                 self.conf.use_namespaces, None)
+                                 self.conf.use_namespaces, {})
         ri.router = {
             'id': _uuid(),
             'admin_state_up': True,
             'routes': [],
-            'external_gateway_info': {}}
+            'external_gateway_info': {},
+            'distributed': False}
         device = mock.Mock()
         self.agent.router_info = {router_id: ri}
         self.agent.devices = [device]
index 132ff1dd43967cde1f62996a153ad54990aabfa2..78f0ff2d43a5520e7b21a4bc7c52ad2c16ba037b 100644 (file)
@@ -37,6 +37,7 @@ _uuid = uuidutils.generate_uuid
 HOSTNAME = 'myhost'
 FAKE_ID = _uuid()
 FAKE_ID_2 = _uuid()
+FIP_PRI = 32768
 
 
 class TestExclusiveRouterProcessor(base.BaseTestCase):
@@ -165,6 +166,14 @@ class TestBasicRouterOperations(base.BaseTestCase):
         self.mock_ip = mock.MagicMock()
         ip_cls.return_value = self.mock_ip
 
+        ip_rule = mock.patch('neutron.agent.linux.ip_lib.IpRule').start()
+        self.mock_rule = mock.MagicMock()
+        ip_rule.return_value = self.mock_rule
+
+        ip_dev = mock.patch('neutron.agent.linux.ip_lib.IPDevice').start()
+        self.mock_ip_dev = mock.MagicMock()
+        ip_dev.return_value = self.mock_ip_dev
+
         self.l3pluginApi_cls_p = mock.patch(
             'neutron.agent.l3_agent.L3PluginApi')
         l3pluginApi_cls = self.l3pluginApi_cls_p.start()
@@ -175,6 +184,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
             'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall')
         self.looping_call_p.start()
 
+        self.subnet_id_list = []
+
     def test__sync_routers_task_raise_exception(self):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         self.plugin_api.get_routers.side_effect = Exception()
@@ -192,7 +203,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
     def test_router_info_create(self):
         id = _uuid()
         ri = l3_agent.RouterInfo(id, self.conf.root_helper,
-                                 self.conf.use_namespaces, None)
+                                 self.conf.use_namespaces, {})
 
         self.assertTrue(ri.ns_name.endswith(id))
 
@@ -221,24 +232,28 @@ class TestBasicRouterOperations(base.BaseTestCase):
         port_id = _uuid()
         router_id = _uuid()
         network_id = _uuid()
+        router = self._prepare_router_data(num_internal_ports=2)
+        router_id = router['id']
         ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
-                                 self.conf.use_namespaces, None)
+                                 self.conf.use_namespaces, router=router)
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         cidr = '99.0.1.9/24'
         mac = 'ca:fe:de:ad:be:ef'
+        port = {'network_id': network_id,
+                'id': port_id, 'ip_cidr': cidr,
+                'mac_address': mac}
         interface_name = agent.get_internal_device_name(port_id)
 
         if action == 'add':
             self.device_exists.return_value = False
-            agent.internal_network_added(ri, network_id,
-                                         port_id, cidr, mac)
+            agent.internal_network_added(ri, port)
             self.assertEqual(self.mock_driver.plug.call_count, 1)
             self.assertEqual(self.mock_driver.init_l3.call_count, 1)
-            self.send_arp.assert_called_once_with(ri, interface_name,
+            self.send_arp.assert_called_once_with(ri.ns_name, interface_name,
                                                   '99.0.1.9')
         elif action == 'remove':
             self.device_exists.return_value = True
-            agent.internal_network_removed(ri, port_id, cidr)
+            agent.internal_network_removed(ri, port)
             self.assertEqual(self.mock_driver.unplug.call_count, 1)
         else:
             raise Exception("Invalid action %s" % action)
@@ -250,9 +265,9 @@ class TestBasicRouterOperations(base.BaseTestCase):
         self._test_internal_network_action('remove')
 
     def _test_external_gateway_action(self, action):
-        router_id = _uuid()
-        ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
-                                 self.conf.use_namespaces, None)
+        router = self._prepare_router_data(num_internal_ports=2)
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         internal_cidrs = ['100.0.1.0/24', '200.74.0.0/16']
         ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
@@ -267,17 +282,19 @@ class TestBasicRouterOperations(base.BaseTestCase):
 
         if action == 'add':
             self.device_exists.return_value = False
-            ri.router = mock.Mock()
-            ri.router.get.return_value = [{'floating_ip_address':
-                                           '192.168.1.34'}]
+            fake_fip = {'floatingips': [{'id': _uuid(),
+                                         'floating_ip_address': '192.168.1.34',
+                                         'fixed_ip_address': '192.168.0.1',
+                                         'port_id': _uuid()}]}
+            router[l3_constants.FLOATINGIP_KEY] = fake_fip['floatingips']
             agent.external_gateway_added(ri, ex_gw_port,
                                          interface_name, internal_cidrs)
             self.assertEqual(self.mock_driver.plug.call_count, 1)
             self.assertEqual(self.mock_driver.init_l3.call_count, 1)
-            self.send_arp.assert_called_once_with(ri, interface_name,
+            self.send_arp.assert_called_once_with(ri.ns_name, interface_name,
                                                   '20.0.0.30')
             kwargs = {'preserve_ips': ['192.168.1.34/32'],
-                      'namespace': 'qrouter-' + router_id,
+                      'namespace': 'qrouter-' + router['id'],
                       'gateway': '20.0.0.1',
                       'extra_subnets': [{'cidr': '172.16.0.0/24'}]}
             self.mock_driver.init_l3.assert_called_with(interface_name,
@@ -301,7 +318,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
 
         router_id = _uuid()
         ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
-                                 self.conf.use_namespaces, None)
+                                 self.conf.use_namespaces, {})
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         floating_ip = '20.0.0.101'
         interface_name = agent.get_external_device_name(router_id)
@@ -335,7 +352,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
         router_id = _uuid()
         ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
                                  self.conf.use_namespaces,
-                                 None)
+                                 {})
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
 
         fake_route1 = {'destination': '135.207.0.0/16',
@@ -383,7 +400,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
 
         ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
                                  self.conf.use_namespaces,
-                                 None)
+                                 {})
         ri.router = {}
 
         fake_old_routes = []
@@ -446,7 +463,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
                 self.assertIn(r.rule, expected_rules)
 
     @staticmethod
-    def _router_append_interface(router, count=1, ip_version=4,
+    def _router_append_interface(router, subnet_id_list=[], count=1,
+                                 ip_version=4,
                                  ra_mode=None, addr_mode=None):
         if ip_version == 4:
             ip_pool = '35.4.%i.4'
@@ -465,12 +483,17 @@ class TestBasicRouterOperations(base.BaseTestCase):
              for p in interfaces])
 
         for i in range(current, current + count):
+            if subnet_id_list:
+                subnet_id_list.append(_uuid())
+                subnet_id = subnet_id_list[i]
+            else:
+                subnet_id = _uuid()
             interfaces.append(
                 {'id': _uuid(),
                  'network_id': _uuid(),
                  'admin_state_up': True,
                  'fixed_ips': [{'ip_address': ip_pool % i,
-                                'subnet_id': _uuid()}],
+                                'subnet_id': subnet_id}],
                  'mac_address': 'ca:fe:de:ad:be:ef',
                  'subnet': {'cidr': cidr_pool % i,
                             'gateway_ip': gw_pool % i,
@@ -497,20 +520,159 @@ class TestBasicRouterOperations(base.BaseTestCase):
                                      'subnet_id': _uuid()}],
                       'subnet': {'cidr': cidr,
                                  'gateway_ip': gateway_ip}}
+        int_ports = []
+        self.subnet_id_list = []
+        for i in range(num_internal_ports):
+            self.subnet_id_list.append(_uuid())
+            subnet_id = self.subnet_id_list[i]
+            int_ports.append({'id': _uuid(),
+                              'network_id': _uuid(),
+                              'admin_state_up': True,
+                              'fixed_ips': [{'ip_address': '35.4.%s.4' % i,
+                                             'subnet_id': subnet_id}],
+                              'mac_address': 'ca:fe:de:ad:be:ef',
+                              'subnet': {'cidr': '35.4.%s.0/24' % i,
+                                         'gateway_ip': '35.4.%s.1' % i}})
 
         router = {
             'id': router_id,
+            'distributed': False,
             l3_constants.INTERFACE_KEY: [],
             'routes': [],
             'gw_port': ex_gw_port}
-        self._router_append_interface(router, count=num_internal_ports,
+        self._router_append_interface(router, self.subnet_id_list,
+                                      count=num_internal_ports,
                                       ip_version=ip_version)
 
         if enable_snat is not None:
             router['enable_snat'] = enable_snat
         return router
 
-    def test_process_router(self):
+    def test__map_internal_interfaces(self):
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        router = self._prepare_router_data(num_internal_ports=4)
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+        test_port = {'mac_address': '00:12:23:34:45:56',
+                     'fixed_ips': [{'subnet_id': self.subnet_id_list[0],
+                                    'ip_address': '101.12.13.14'}]}
+        internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
+        # test valid case
+        res_port = agent._map_internal_interfaces(ri,
+                                                  internal_ports[0],
+                                                  [test_port])
+        self.assertEqual(test_port, res_port)
+        # test invalid case
+        test_port['fixed_ips'][0]['subnet_id'] = 1234
+        res_ip = agent._map_internal_interfaces(ri,
+                                                internal_ports[0],
+                                                [test_port])
+        self.assertNotEqual(test_port, res_ip)
+        self.assertIsNone(res_ip)
+
+    def test_get_internal_port(self):
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        router = self._prepare_router_data(num_internal_ports=4)
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+
+        # Test Basic cases
+        port = agent.get_internal_port(ri, self.subnet_id_list[0])
+        fips = port.get('fixed_ips', [])
+        subnet_id = fips[0]['subnet_id']
+        self.assertEqual(self.subnet_id_list[0], subnet_id)
+        port = agent.get_internal_port(ri, self.subnet_id_list[1])
+        fips = port.get('fixed_ips', [])
+        subnet_id = fips[0]['subnet_id']
+        self.assertEqual(self.subnet_id_list[1], subnet_id)
+        port = agent.get_internal_port(ri, self.subnet_id_list[3])
+        fips = port.get('fixed_ips', [])
+        subnet_id = fips[0]['subnet_id']
+        self.assertEqual(self.subnet_id_list[3], subnet_id)
+
+        # Test miss cases
+        no_port = agent.get_internal_port(ri, FAKE_ID)
+        self.assertIsNone(no_port)
+        port = agent.get_internal_port(ri, self.subnet_id_list[0])
+        fips = port.get('fixed_ips', [])
+        subnet_id = fips[0]['subnet_id']
+        self.assertNotEqual(self.subnet_id_list[3], subnet_id)
+
+    def test__set_subnet_arp_info(self):
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        router = self._prepare_router_data(num_internal_ports=2)
+        router['distributed'] = True
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+        ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
+        test_ports = [{'mac_address': '00:11:22:33:44:55',
+                      'device_owner': 'network:dhcp',
+                      'subnet_id': self.subnet_id_list[0],
+                      'fixed_ips': [{'ip_address': '1.2.3.4'}]}]
+
+        self.plugin_api.get_ports_by_subnet.return_value = test_ports
+
+        # Test basic case
+        ports[0]['subnet']['id'] = self.subnet_id_list[0]
+        agent._set_subnet_arp_info(ri, ports[0])
+        self.mock_ip_dev.neigh.add.assert_called_once_with(
+            4, '1.2.3.4', '00:11:22:33:44:55')
+
+        # Test negative case
+        router['distributed'] = False
+        agent._set_subnet_arp_info(ri, ports[0])
+        self.mock_ip_dev.neigh.add.never_called()
+
+    def test_add_arp_entry(self):
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        router = self._prepare_router_data(num_internal_ports=2)
+        arp_table = {'ip_address': '1.7.23.11',
+                     'mac_address': '00:11:22:33:44:55',
+                     'subnet_id': self.subnet_id_list[0]}
+
+        payload = {'arp_table': arp_table, 'router_id': router['id']}
+        agent._router_added(router['id'], router)
+        agent.add_arp_entry(None, payload)
+        agent.router_deleted(None, router['id'])
+        self.mock_ip_dev.neigh.add.assert_called_once_with(
+            4, '1.7.23.11', '00:11:22:33:44:55')
+
+    def test_del_arp_entry(self):
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        router = self._prepare_router_data(num_internal_ports=2)
+        arp_table = {'ip_address': '1.5.25.15',
+                     'mac_address': '00:44:33:22:11:55',
+                     'subnet_id': self.subnet_id_list[0]}
+
+        payload = {'arp_table': arp_table, 'router_id': router['id']}
+        agent._router_added(router['id'], router)
+        # first add the entry
+        agent.add_arp_entry(None, payload)
+        # now delete it
+        agent.del_arp_entry(None, payload)
+        self.mock_ip_dev.neigh.delete.assert_called_once_with(
+            4, '1.5.25.15', '00:44:33:22:11:55')
+        agent.router_deleted(None, router['id'])
+
+    def test_process_cent_router(self):
+        router = self._prepare_router_data()
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+        self._test_process_router(ri)
+
+    def test_process_dist_router(self):
+        router = self._prepare_router_data()
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+        ri.router['distributed'] = True
+        ri.router['_snat_router_interfaces'] = [{
+            'fixed_ips': [{'subnet_id': self.subnet_id_list[0],
+                           'ip_address': '1.2.3.4'}]}]
+        ri.router['gw_port_host'] = None
+        self._test_process_router(ri)
+
+    def _test_process_router(self, ri):
+        router = ri.router
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         fake_fip_id = 'fake_fip_id'
         agent.process_router_floating_ip_addresses = mock.Mock()
@@ -518,14 +680,11 @@ class TestBasicRouterOperations(base.BaseTestCase):
         agent.process_router_floating_ip_addresses.return_value = {
             fake_fip_id: 'ACTIVE'}
         agent.external_gateway_added = mock.Mock()
-        router = self._prepare_router_data()
         fake_floatingips1 = {'floatingips': [
             {'id': fake_fip_id,
              'floating_ip_address': '8.8.8.8',
              'fixed_ip_address': '7.7.7.7',
              'port_id': _uuid()}]}
-        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
-                                 self.conf.use_namespaces, router=router)
         agent.process_router(ri)
         ex_gw_port = agent._get_ex_gw_port(ri)
         agent.process_router_floating_ip_addresses.assert_called_with(
@@ -566,21 +725,13 @@ class TestBasicRouterOperations(base.BaseTestCase):
         self.assertFalse(agent.process_router_floating_ip_nat_rules.called)
 
     @mock.patch('neutron.agent.linux.ip_lib.IPDevice')
-    def test_process_router_floating_ip_addresses_add(self, IPDevice):
-        fip_id = _uuid()
-        fip = {
-            'id': fip_id, 'port_id': _uuid(),
-            'floating_ip_address': '15.1.2.3',
-            'fixed_ip_address': '192.168.0.1'
-        }
-
+    def _test_process_router_floating_ip_addresses_add(self, ri,
+                                                       agent, IPDevice):
+        floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
+        fip_id = floating_ips[0]['id']
         IPDevice.return_value = device = mock.Mock()
         device.addr.list.return_value = []
-
-        ri = mock.MagicMock()
-        ri.router.get.return_value = [fip]
-
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
 
         fip_statuses = agent.process_router_floating_ip_addresses(
             ri, {'id': _uuid()})
@@ -597,6 +748,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
 
         ri = mock.MagicMock()
         ri.router.get.return_value = [fip]
+        ri.router['distributed'].__nonzero__ = lambda self: False
 
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
 
@@ -608,6 +760,50 @@ class TestBasicRouterOperations(base.BaseTestCase):
         for chain, rule in rules:
             nat.add_rule.assert_any_call(chain, rule, tag='floating_ip')
 
+    def test_process_router_cent_floating_ip_add(self):
+        fake_floatingips = {'floatingips': [
+            {'id': _uuid(),
+             'floating_ip_address': '15.1.2.3',
+             'fixed_ip_address': '192.168.0.1',
+             'port_id': _uuid()}]}
+
+        router = self._prepare_router_data(enable_snat=True)
+        router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips']
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+        ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        self._test_process_router_floating_ip_addresses_add(ri, agent)
+
+    def test_process_router_dist_floating_ip_add(self):
+        fake_floatingips = {'floatingips': [
+            {'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()}]}
+
+        router = self._prepare_router_data(enable_snat=True)
+        router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips']
+        router['distributed'] = True
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+        ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        agent.host = HOSTNAME
+        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'}
+        )
+        self._test_process_router_floating_ip_addresses_add(ri, agent)
+
+    # TODO(mrsmith): refactor for DVR cases
     @mock.patch('neutron.agent.linux.ip_lib.IPDevice')
     def test_process_router_floating_ip_addresses_remove(self, IPDevice):
         IPDevice.return_value = device = mock.Mock()
@@ -615,6 +811,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
 
         ri = mock.MagicMock()
         ri.router.get.return_value = []
+        ri.router['distributed'].__nonzero__ = lambda self: False
 
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
 
@@ -647,6 +844,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
         IPDevice.return_value = device = mock.Mock()
         device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}]
         ri = mock.MagicMock()
+        ri.router['distributed'].__nonzero__ = lambda self: False
 
         ri.router.get.return_value = [fip]
 
@@ -693,6 +891,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
         }
         ri = mock.MagicMock()
         ri.router.get.return_value = [fip]
+        ri.router['distributed'].__nonzero__ = lambda self: False
 
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
 
@@ -1035,6 +1234,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         ri = mock.MagicMock()
         port = {'fixed_ips': [{'ip_address': '192.168.1.4'}]}
+        ri.router = {'distributed': False}
 
         agent._handle_router_snat_rules(ri, port, [], "iface", "add_rules")
 
@@ -1051,9 +1251,10 @@ class TestBasicRouterOperations(base.BaseTestCase):
     def test_handle_router_snat_rules_add_rules(self):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         ri = l3_agent.RouterInfo(_uuid(), self.conf.root_helper,
-                                 self.conf.use_namespaces, None)
+                                 self.conf.use_namespaces, {})
         ex_gw_port = {'fixed_ips': [{'ip_address': '192.168.1.4'}]}
         internal_cidrs = ['10.0.0.0/24']
+        ri.router = {'distributed': False}
         agent._handle_router_snat_rules(ri, ex_gw_port, internal_cidrs,
                                         "iface", "add_rules")
 
@@ -1116,11 +1317,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
             self.assertFalse(external_gateway_removed.called)
             self.assertFalse(internal_network_removed.called)
             internal_network_added.assert_called_once_with(
-                ri,
-                internal_port['network_id'],
-                internal_port['id'],
-                internal_port['ip_cidr'],
-                internal_port['mac_address'])
+                ri, internal_port)
             self.assertEqual(self.mock_driver.unplug.call_count,
                              len(stale_devnames))
             calls = [mock.call(stale_devname,
@@ -1193,11 +1390,32 @@ class TestBasicRouterOperations(base.BaseTestCase):
             'enable_snat': True,
             'routes': [],
             'gw_port': ex_gw_port}
+        router['distributed'] = False
         agent._router_added(router['id'], router)
         agent.router_deleted(None, router['id'])
         agent._process_router_delete()
         self.assertFalse(list(agent.removed_routers))
 
+    def test_destroy_fip_namespace(self):
+        class FakeDev(object):
+            def __init__(self, name):
+                self.name = name
+
+        namespaces = ['qrouter-foo', 'qrouter-bar']
+
+        self.mock_ip.get_namespaces.return_value = namespaces
+        self.mock_ip.get_devices.return_value = [FakeDev('fr-aaaa'),
+                                                 FakeDev('fg-aaaa')]
+
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+
+        agent._destroy_fip_namespace(namespaces[0])
+        # TODO(mrsmith): update for fr interface
+        self.assertEqual(self.mock_driver.unplug.call_count, 1)
+        self.mock_driver.unplug.assert_called_with('fg-aaaa', bridge='br-ex',
+                                                   prefix='fg-',
+                                                   namespace='qrouter-foo')
+
     def test_destroy_router_namespace_skips_ns_removal(self):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         agent._destroy_router_namespace("fakens")
@@ -1216,7 +1434,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
         router_id = _uuid()
         router = {'id': _uuid(),
                   'external_gateway_info': {},
-                  'routes': []}
+                  'routes': [],
+                  'distributed': False}
         with mock.patch.object(
             agent, '_destroy_metadata_proxy') as destroy_proxy:
             with mock.patch.object(
@@ -1439,7 +1658,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
         self.conf.set_override('router_id', None)
         stale_namespaces = [l3_agent.NS_PREFIX + 'cccc',
                             l3_agent.NS_PREFIX + 'eeeee']
-        router_list = [{'id': 'foo'}, {'id': 'aaaa'}]
+        router_list = [{'id': 'foo', 'distributed': False},
+                       {'id': 'aaaa', 'distributed': False}]
         other_namespaces = ['qdhcp-aabbcc', 'unknown']
 
         self._cleanup_namespace_test(stale_namespaces,
@@ -1451,13 +1671,166 @@ class TestBasicRouterOperations(base.BaseTestCase):
         stale_namespaces = [l3_agent.NS_PREFIX + 'cccc',
                             l3_agent.NS_PREFIX + 'eeeee',
                             l3_agent.NS_PREFIX + self.conf.router_id]
-        router_list = [{'id': 'foo'}, {'id': 'aaaa'}]
+        router_list = [{'id': 'foo', 'distributed': False},
+                       {'id': 'aaaa', 'distributed': False}]
         other_namespaces = ['qdhcp-aabbcc', 'unknown']
 
         self._cleanup_namespace_test(stale_namespaces,
                                      router_list,
                                      other_namespaces)
 
+    def test_create_dvr_gateway(self):
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        router = self._prepare_router_data()
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+
+        port_id = _uuid()
+        dvr_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': _uuid(),
+                       'mac_address': 'ca:fe:de:ad:be:ef',
+                       'ip_cidr': '20.0.0.30/24'}
+
+        snat_ports = [{'subnet': {'cidr': '152.2.0.0/16',
+                                  'gateway_ip': '152.2.0.1',
+                                  'id': _uuid()},
+                       'network_id': _uuid(),
+                       'device_owner': 'network:router_centralized_snat',
+                       'ip_cidr': '152.2.0.13/16',
+                       'mac_address': 'fa:16:3e:80:8d:80',
+                       'fixed_ips': [{'subnet_id': _uuid(),
+                                      'ip_address': '152.2.0.13'}],
+                       'id': _uuid(), 'device_id': _uuid()},
+                      {'subnet': {'cidr': '152.10.0.0/16',
+                                  'gateway_ip': '152.10.0.1',
+                                  'id': _uuid()},
+                       'network_id': _uuid(),
+                       'device_owner': 'network:router_centralized_snat',
+                       'ip_cidr': '152.10.0.13/16',
+                       'mac_address': 'fa:16:3e:80:8d:80',
+                       'fixed_ips': [{'subnet_id': _uuid(),
+                                     'ip_address': '152.10.0.13'}],
+                       'id': _uuid(), 'device_id': _uuid()}]
+
+        interface_name = agent.get_snat_int_device_name(port_id)
+        internal_cidrs = None
+        self.device_exists.return_value = False
+
+        agent._create_dvr_gateway(ri, dvr_gw_port, interface_name,
+                                  internal_cidrs, snat_ports)
+
+        # check 2 internal ports are plugged
+        # check 1 ext-gw-port is plugged
+        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')
+        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 = self._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 = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+
+        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']))
+
+        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(
+            ri.fip_2_rtr, table=16)
+
+    # TODO(mrsmith): test _create_agent_gateway_port
+
+    def test_floating_ip_added_dist(self):
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        router = self._prepare_router_data()
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+        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(),
+                         'mac_address': 'ca:fe:de:ad:be:ef',
+                         'ip_cidr': '20.0.0.30/24'}
+
+        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()}
+        agent.agent_gateway_port = agent_gw_port
+        agent.floating_ip_added_dist(ri, fip)
+        self.mock_rule.add_rule_from.assert_called_with('192.168.0.1',
+                                                        16, FIP_PRI)
+        # TODO(mrsmith): add more asserts
+
+    def test_floating_ip_removed_dist(self):
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        router = self._prepare_router_data()
+        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(),
+                         'mac_address': 'ca:fe:de:ad:be:ef',
+                         'ip_cidr': '20.0.0.30/24'}
+
+        fip_cidr = '11.22.33.44/24'
+
+        ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+                                 self.conf.use_namespaces, router=router)
+        ri.dist_fip_count = 2
+        ri.floating_ips_dict['11.22.33.44'] = FIP_PRI
+        agent.agent_gateway_port = agent_gw_port
+        agent.floating_ip_removed_dist(ri, fip_cidr)
+        self.mock_rule.delete_rule_priority.assert_called_with(FIP_PRI)
+        self.mock_ip_dev.route.delete_route.assert_called_with(fip_cidr,
+                                                               ri.rtr_2_fip)
+        # TODO(mrsmith): test ri.dist_fip_count == 0
+        # TODO(mrsmith): test agent_fip_count == 0 case
+
 
 class TestL3AgentEventHandler(base.BaseTestCase):