]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Move external port processing to router classes
authorCarl Baldwin <carl.baldwin@hp.com>
Mon, 23 Feb 2015 23:01:47 +0000 (23:01 +0000)
committerCarl Baldwin <carl.baldwin@hp.com>
Mon, 23 Mar 2015 22:51:07 +0000 (22:51 +0000)
Change-Id: I5f0682c68c9b820393f3b5e9eb126391a06da775
Partially-Implements: bp/restructure-l3-agent

neutron/agent/l3/agent.py
neutron/agent/l3/dvr.py
neutron/agent/l3/dvr_router.py
neutron/agent/l3/ha_router.py
neutron/agent/l3/router_info.py
neutron/tests/functional/agent/test_l3_agent.py
neutron/tests/unit/test_l3_agent.py

index 692ab9d16c75852a53437777acd3c1cd4bbe9231..2b9197241f360ec95677df39615b1852b548b59b 100644 (file)
@@ -14,7 +14,6 @@
 #
 
 import eventlet
-import netaddr
 from oslo_config import cfg
 from oslo_log import log as logging
 import oslo_messaging
@@ -331,52 +330,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         self.event_observers.notify(
             adv_svc.AdvancedService.after_router_removed, ri)
 
-    def _process_external_gateway(self, ri):
-        ex_gw_port = ri.get_ex_gw_port()
-        ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or
-                         ri.ex_gw_port and ri.ex_gw_port['id'])
-
-        interface_name = None
-        if ex_gw_port_id:
-            interface_name = self.get_external_device_name(ex_gw_port_id)
-        if ex_gw_port:
-            def _gateway_ports_equal(port1, port2):
-                def _get_filtered_dict(d, ignore):
-                    return dict((k, v) for k, v in d.iteritems()
-                                if k not in ignore)
-
-                keys_to_ignore = set(['binding:host_id'])
-                port1_filtered = _get_filtered_dict(port1, keys_to_ignore)
-                port2_filtered = _get_filtered_dict(port2, keys_to_ignore)
-                return port1_filtered == port2_filtered
-
-            ri._set_subnet_info(ex_gw_port)
-            if not ri.ex_gw_port:
-                self.external_gateway_added(ri, ex_gw_port, interface_name)
-            elif not _gateway_ports_equal(ex_gw_port, ri.ex_gw_port):
-                self.external_gateway_updated(ri, ex_gw_port, interface_name)
-        elif not ex_gw_port and ri.ex_gw_port:
-            self.external_gateway_removed(ri, ri.ex_gw_port, interface_name)
-
-        existing_devices = ri._get_existing_devices()
-        stale_devs = [dev for dev in existing_devices
-                      if dev.startswith(EXTERNAL_DEV_PREFIX)
-                      and dev != interface_name]
-        for stale_dev in stale_devs:
-            LOG.debug('Deleting stale external router device: %s',
-                      stale_dev)
-            self.driver.unplug(stale_dev,
-                               bridge=self.conf.external_network_bridge,
-                               namespace=ri.ns_name,
-                               prefix=EXTERNAL_DEV_PREFIX)
-
-        # Process SNAT rules for external gateway
-        if (not ri.router['distributed'] or
-            ex_gw_port and ri.get_gw_port_host() == self.host):
-            ri.perform_snat_action(self._handle_router_snat_rules,
-                                   interface_name)
-
-    def _update_fip_statuses(self, ri, existing_floating_ips, fip_statuses):
+    def update_fip_statuses(self, ri, existing_floating_ips, fip_statuses):
         # Identify floating IPs which were disabled
         ri.floating_ips = set(fip_statuses.keys())
         for fip_id in existing_floating_ips - ri.floating_ips:
@@ -393,36 +347,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
             else:
                 ri.disable_keepalived()
 
-    def _process_external(self, ri):
-        existing_floating_ips = ri.floating_ips
-        try:
-            with ri.iptables_manager.defer_apply():
-                self._process_external_gateway(ri)
-                ex_gw_port = ri.get_ex_gw_port()
-                # TODO(Carl) Return after setting existing_floating_ips and
-                # still call _update_fip_statuses?
-                if not ex_gw_port:
-                    return
-
-                # Process SNAT/DNAT rules and addresses for floating IPs
-                if ri.router['distributed']:
-                    self.create_dvr_fip_interfaces(ri, ex_gw_port)
-                ri.process_snat_dnat_for_fip()
-
-            # Once NAT rules for floating IPs are safely in place
-            # configure their addresses on the external gateway port
-            interface_name = self._get_external_device_interface_name(
-                ri, ex_gw_port)
-            fip_statuses = ri.configure_fip_addresses(interface_name)
-
-        except (n_exc.FloatingIpSetupException,
-                n_exc.IpTablesApplyException) as e:
-                # All floating IPs must be put in error state
-                LOG.exception(e)
-                fip_statuses = ri.put_fips_in_error_state()
-
-        self._update_fip_statuses(ri, existing_floating_ips, fip_statuses)
-
     @common_utils.exception_logger()
     def process_router(self, ri):
         # TODO(mrsmith) - we shouldn't need to check here
@@ -433,7 +357,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
             ri.fip_ns = self.get_fip_ns(ex_gw_port['network_id'])
             ri.fip_ns.scan_fip_ports(ri)
         ri._process_internal_ports()
-        self._process_external(ri)
+        ri.process_external(self)
         # Process static routes for router
         ri.routes_updated()
 
@@ -445,203 +369,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         ri.snat_ports = ri.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, [])
         ri.enable_snat = ri.router.get('enable_snat')
 
-    def _handle_router_snat_rules(self, ri, ex_gw_port,
-                                  interface_name, action):
-        # 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 SNAT rules will be in their own namespace
-        if not ri.router['distributed']:
-            iptables_manager = ri.iptables_manager
-        elif ri.snat_iptables_manager:
-            iptables_manager = ri.snat_iptables_manager
-        else:
-            LOG.debug("DVR router: no snat rules to be handled")
-            return
-
-        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 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
-            for ip_addr in ex_gw_port['fixed_ips']:
-                ex_gw_ip = ip_addr['ip_address']
-                if netaddr.IPAddress(ex_gw_ip).version == 4:
-                    rules = self.external_gateway_nat_rules(ex_gw_ip,
-                                                            interface_name)
-                    for rule in rules:
-                        iptables_manager.ipv4['nat'].add_rule(*rule)
-                    break
-        iptables_manager.apply()
-
-    def create_dvr_fip_interfaces(self, ri, ex_gw_port):
-        floating_ips = ri.get_floating_ips()
-        fip_agent_port = ri.get_floating_agent_gw_interface(
-            ex_gw_port['network_id'])
-        LOG.debug("FloatingIP agent gateway port received from the plugin: "
-                  "%s", fip_agent_port)
-        if floating_ips:
-            is_first = ri.fip_ns.subscribe(ri.router_id)
-            if is_first and fip_agent_port:
-                if 'subnet' not in fip_agent_port:
-                    LOG.error(_LE('Missing subnet/agent_gateway_port'))
-                else:
-                    ri._set_subnet_info(fip_agent_port)
-                    ri.fip_ns.create_gateway_port(fip_agent_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 = ri.fip_ns.get_int_device_name(ri.router_id)
-            if ip_lib.device_exists(fip_int, 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'])
-
-    def get_external_device_name(self, port_id):
-        return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
-
-    def external_gateway_added(self, ri, ex_gw_port, interface_name):
-        if ri.router['distributed']:
-            ip_wrapr = ip_lib.IPWrapper(namespace=ri.ns_name)
-            ip_wrapr.netns.execute(['sysctl', '-w',
-                                   'net.ipv4.conf.all.send_redirects=0'])
-            snat_ports = ri.get_snat_interfaces()
-            for p in ri.internal_ports:
-                gateway = ri._map_internal_interfaces(p, snat_ports)
-                id_name = ri.get_internal_device_name(p['id'])
-                if gateway:
-                    ri._snat_redirect_add(
-                        gateway['fixed_ips'][0]['ip_address'], p, id_name)
-
-            if (self.conf.agent_mode == l3_constants.L3_AGENT_MODE_DVR_SNAT and
-                ri.get_gw_port_host() == self.host):
-                self._create_dvr_gateway(ri, ex_gw_port, interface_name,
-                                         snat_ports)
-            for port in snat_ports:
-                for ip in port['fixed_ips']:
-                    ri._update_arp_entry(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.get_floating_ips()
-        preserve_ips = [common_utils.ip_to_cidr(ip['floating_ip_address'])
-                        for ip in floating_ips]
-
-        self._external_gateway_added(ri, ex_gw_port, interface_name,
-                                     ri.ns_name, preserve_ips)
-
-        if ri.is_ha:
-            ri._ha_external_gateway_added(ex_gw_port, interface_name)
-            ri._ha_disable_addressing_on_interface(interface_name)
-
-    def external_gateway_updated(self, ri, ex_gw_port, interface_name):
-        preserve_ips = []
-        if ri.router['distributed']:
-            if (self.conf.agent_mode == l3_constants.L3_AGENT_MODE_DVR_SNAT and
-                ri.get_gw_port_host() == self.host):
-                ns_name = ri.snat_namespace.name
-            else:
-                # no centralized SNAT gateway for this node/agent
-                LOG.debug("not hosting snat for router: %s", ri.router['id'])
-                return
-        else:
-            ns_name = ri.ns_name
-            floating_ips = ri.get_floating_ips()
-            preserve_ips = [common_utils.ip_to_cidr(ip['floating_ip_address'])
-                            for ip in floating_ips]
-
-        self._external_gateway_added(ri, ex_gw_port, interface_name,
-                                     ns_name, preserve_ips)
-
-        if ri.is_ha:
-            ri._ha_external_gateway_updated(ex_gw_port, interface_name)
-
-    def _external_gateway_added(self, ri, ex_gw_port, interface_name,
-                                ns_name, preserve_ips):
-        if not ip_lib.device_exists(interface_name, 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)
-
-        if not ri.is_ha:
-            self.driver.init_l3(
-                interface_name, [ex_gw_port['ip_cidr']], 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]
-            ip_lib.send_gratuitous_arp(ns_name,
-                                       interface_name,
-                                       ip_address,
-                                       self.conf.send_arp_for_ha)
-
-    def external_gateway_removed(self, ri, ex_gw_port, interface_name):
-        if ri.router['distributed']:
-            # TODO(Carl) Should this be calling process_snat_dnat_for_fip?
-            ri.process_floating_ip_nat_rules()
-            if ri.fip_ns:
-                to_fip_interface_name = (
-                    self._get_external_device_interface_name(ri, ex_gw_port))
-                ri.process_floating_ip_addresses(to_fip_interface_name)
-            snat_ports = ri.get_snat_interfaces()
-            for p in ri.internal_ports:
-                gateway = self._map_internal_interfaces(ri, p, snat_ports)
-                internal_interface = ri.get_internal_device_name(p['id'])
-                ri._snat_redirect_remove(gateway['fixed_ips'][0]['ip_address'],
-                                         p,
-                                         internal_interface)
-
-            if (self.conf.agent_mode == l3_constants.L3_AGENT_MODE_DVR_SNAT
-                and ri.get_gw_port_host() == self.host):
-                ns_name = ri.snat_namespace.name
-            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
-
-        if ri.is_ha:
-            ri._ha_external_gateway_removed(interface_name)
-
-        self.driver.unplug(interface_name,
-                           bridge=self.conf.external_network_bridge,
-                           namespace=ns_name,
-                           prefix=EXTERNAL_DEV_PREFIX)
-        if ri.router['distributed']:
-            ri.delete_snat_namespace()
-
-    def external_gateway_nat_rules(self, ex_gw_ip, interface_name):
-        rules = [('POSTROUTING', '! -i %(interface_name)s '
-                  '! -o %(interface_name)s -m conntrack ! '
-                  '--ctstate DNAT -j ACCEPT' %
-                  {'interface_name': interface_name}),
-                 ('snat', '-o %s -j SNAT --to-source %s' %
-                  (interface_name, ex_gw_ip))]
-        return rules
-
     def router_deleted(self, context, router_id):
         """Deal with router deletion RPC message."""
         LOG.debug('Got router deleted notification for %s', router_id)
index d6829b63e87f3ab5a2e657d860e8992f8c4b53c3..64c75f7f3903023724d0215612d657bda158ebc8 100644 (file)
@@ -18,7 +18,6 @@ from oslo_log import log as logging
 
 from neutron.agent.l3 import dvr_fip_ns
 from neutron.agent.l3 import dvr_snat_ns
-from neutron.agent.linux import iptables_manager
 
 LOG = logging.getLogger(__name__)
 
@@ -58,28 +57,6 @@ class AgentMixin(object):
     def get_ports_by_subnet(self, subnet_id):
         return self.plugin_rpc.get_ports_by_subnet(self.context, subnet_id)
 
-    def _create_dvr_gateway(self, ri, ex_gw_port, gw_interface_name,
-                            snat_ports):
-        """Create SNAT namespace."""
-        snat_ns = ri.create_snat_namespace()
-        # connect snat_ports to br_int from SNAT namespace
-        for port in snat_ports:
-            # create interface_name
-            ri._set_subnet_info(port)
-            interface_name = ri.get_snat_int_device_name(port['id'])
-            # TODO(Carl) calling private method on router.  Will fix soon.
-            ri._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,
-                                     snat_ns.name, preserve_ips=[])
-        ri.snat_iptables_manager = iptables_manager.IptablesManager(
-            namespace=snat_ns.name,
-            use_ipv6=self.use_ipv6)
-        # kicks the FW Agent to add rules for the snat namespace
-        self.process_router_add(ri)
-
     def add_arp_entry(self, context, payload):
         """Add arp entry into router namespace.  Called from RPC."""
         router_id = payload['router_id']
index 6af1e67adc2134fb57f91c04b0e1763048fc52e2..0c9e9139f1b088736f16f7eeb80c034cca122a3e 100644 (file)
@@ -22,6 +22,7 @@ from neutron.agent.l3 import dvr_fip_ns
 from neutron.agent.l3 import dvr_snat_ns
 from neutron.agent.l3 import router_info as router
 from neutron.agent.linux import ip_lib
+from neutron.agent.linux import iptables_manager
 from neutron.common import constants as l3_constants
 from neutron.common import utils as common_utils
 from neutron.i18n import _LE
@@ -170,13 +171,6 @@ class DvrRouter(router.RouterInfo):
         self.snat_namespace.create()
         return self.snat_namespace
 
-    def delete_snat_namespace(self):
-        # TODO(mlavalle): in the near future, this method should contain the
-        # code in the L3 agent that removes an external gateway for a dvr. The
-        # first step is to move the deletion of the snat namespace here
-        self.snat_namespace.delete()
-        self.snat_namespace = None
-
     def _get_internal_port(self, subnet_id):
         """Return internal router port based on subnet_id."""
         router_ports = self.router.get(l3_constants.INTERFACE_KEY, [])
@@ -291,6 +285,12 @@ class DvrRouter(router.RouterInfo):
                       self.router['id'])
         return host
 
+    def _is_this_snat_host(self):
+        # TODO(Carl) This is a sign that dvr needs two router classes.
+        mode = self.agent_conf.agent_mode
+        return (mode == l3_constants.L3_AGENT_MODE_DVR_SNAT
+                and self.get_gw_port_host() == self.host)
+
     def internal_network_added(self, port):
         super(DvrRouter, self).internal_network_added(port)
 
@@ -308,10 +308,7 @@ class DvrRouter(router.RouterInfo):
                                 port,
                                 interface_name)
 
-        # TODO(Carl) This is a sign that dvr needs two router classes.
-        is_this_snat_host = (self.agent_conf.agent_mode == 'dvr_snat' and
-            self.get_gw_port_host() == self.host)
-        if not is_this_snat_host:
+        if not self._is_this_snat_host():
             return
 
         ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(self.router['id'])
@@ -342,8 +339,9 @@ class DvrRouter(router.RouterInfo):
                                    port,
                                    interface_name)
 
-        is_this_snat_host = (self.agent_conf.agent_mode == 'dvr_snat' and
-            self.ex_gw_port['binding:host_id'] == self.host)
+        mode = self.agent_conf.agent_mode
+        is_this_snat_host = (mode == l3_constants.L3_AGENT_MODE_DVR_SNAT
+            and self.ex_gw_port['binding:host_id'] == self.host)
         if not is_this_snat_host:
             return
 
@@ -364,3 +362,139 @@ class DvrRouter(router.RouterInfo):
         fip_ports = self.router.get(l3_constants.FLOATINGIP_AGENT_INTF_KEY, [])
         return next(
             (p for p in fip_ports if p['network_id'] == ext_net_id), None)
+
+    def get_external_device_interface_name(self, ex_gw_port):
+        fip_int = self.fip_ns.get_int_device_name(self.router_id)
+        if ip_lib.device_exists(fip_int, namespace=self.fip_ns.get_name()):
+            return self.fip_ns.get_rtr_ext_device_name(self.router_id)
+
+    def _create_dvr_gateway(self, ex_gw_port, gw_interface_name,
+                            snat_ports):
+        """Create SNAT namespace."""
+        snat_ns = self.create_snat_namespace()
+        # 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,
+                                         dvr_snat_ns.SNAT_INT_DEV_PREFIX)
+        self._external_gateway_added(ex_gw_port, gw_interface_name,
+                                     snat_ns.name, preserve_ips=[])
+        self.snat_iptables_manager = iptables_manager.IptablesManager(
+            namespace=snat_ns.name,
+            use_ipv6=self.use_ipv6)
+        # kicks the FW Agent to add rules for the snat namespace
+        self.agent.process_router_add(self)
+
+    def external_gateway_added(self, ex_gw_port, interface_name):
+        # TODO(Carl) Refactor external_gateway_added/updated/removed to use
+        # super class implementation where possible.  Looks like preserve_ips,
+        # and ns_name are the key differences.
+        ip_wrapr = ip_lib.IPWrapper(namespace=self.ns_name)
+        ip_wrapr.netns.execute(['sysctl', '-w',
+                               'net.ipv4.conf.all.send_redirects=0'])
+        snat_ports = self.get_snat_interfaces()
+        for p in self.internal_ports:
+            gateway = self._map_internal_interfaces(p, snat_ports)
+            id_name = self.get_internal_device_name(p['id'])
+            if gateway:
+                self._snat_redirect_add(
+                    gateway['fixed_ips'][0]['ip_address'], p, id_name)
+
+        if self._is_this_snat_host():
+            self._create_dvr_gateway(ex_gw_port, interface_name, snat_ports)
+
+        for port in snat_ports:
+            for ip in port['fixed_ips']:
+                self._update_arp_entry(ip['ip_address'],
+                                       port['mac_address'],
+                                       ip['subnet_id'],
+                                       'add')
+
+    def external_gateway_updated(self, ex_gw_port, interface_name):
+        if not self._is_this_snat_host():
+            # no centralized SNAT gateway for this node/agent
+            LOG.debug("not hosting snat for router: %s", self.router['id'])
+            return
+
+        self._external_gateway_added(ex_gw_port,
+                                     interface_name,
+                                     self.snat_namespace.name,
+                                     preserve_ips=[])
+
+    def external_gateway_removed(self, ex_gw_port, interface_name):
+        # TODO(Carl) Should this be calling process_snat_dnat_for_fip?
+        self.process_floating_ip_nat_rules()
+        if self.fip_ns:
+            to_fip_interface_name = (
+                self.get_external_device_interface_name(ex_gw_port))
+            self.process_floating_ip_addresses(to_fip_interface_name)
+        snat_ports = self.get_snat_interfaces()
+        for p in self.internal_ports:
+            gateway = self._map_internal_interfaces(p, snat_ports)
+            internal_interface = self.get_internal_device_name(p['id'])
+            self._snat_redirect_remove(gateway['fixed_ips'][0]['ip_address'],
+                                       p,
+                                       internal_interface)
+
+        if not self._is_this_snat_host():
+            # no centralized SNAT gateway for this node/agent
+            LOG.debug("not hosting snat for router: %s", self.router['id'])
+            return
+
+        self.driver.unplug(interface_name,
+                           bridge=self.agent_conf.external_network_bridge,
+                           namespace=self.snat_namespace.name,
+                           prefix=router.EXTERNAL_DEV_PREFIX)
+
+        self.snat_namespace.delete()
+        self.snat_namespace = None
+
+    def _handle_router_snat_rules(self, ex_gw_port,
+                                  interface_name, action):
+        if not self.snat_iptables_manager:
+            LOG.debug("DVR router: no snat rules to be handled")
+            return
+
+        with self.snat_iptables_manager.defer_apply():
+            self._empty_snat_chains(self.snat_iptables_manager)
+
+            # NOTE DVR doesn't add the jump to float snat like the super class.
+
+            self._add_snat_rules(ex_gw_port, self.snat_iptables_manager,
+                                 interface_name, action)
+
+    def perform_snat_action(self, snat_callback, *args):
+        # NOTE DVR skips this step in a few cases...
+        if not self.get_ex_gw_port():
+            return
+        if self.get_gw_port_host() != self.host:
+            return
+
+        super(DvrRouter, self).perform_snat_action(snat_callback, *args)
+
+    def create_dvr_fip_interfaces(self, ex_gw_port):
+        floating_ips = self.get_floating_ips()
+        fip_agent_port = self.get_floating_agent_gw_interface(
+            ex_gw_port['network_id'])
+        LOG.debug("FloatingIP agent gateway port received from the plugin: "
+                  "%s", fip_agent_port)
+        if floating_ips:
+            is_first = self.fip_ns.subscribe(self.router_id)
+            if is_first and fip_agent_port:
+                if 'subnet' not in fip_agent_port:
+                    LOG.error(_LE('Missing subnet/agent_gateway_port'))
+                else:
+                    self._set_subnet_info(fip_agent_port)
+                    self.fip_ns.create_gateway_port(fip_agent_port)
+
+        if self.fip_ns.agent_gateway_port and floating_ips:
+            if self.dist_fip_count == 0:
+                self.fip_ns.create_rtr_2_fip_link(self)
+
+                # kicks the FW Agent to add rules for the IR namespace if
+                # configured
+                self.agent.process_router_add(self)
index d28c6c6d65524f33b0f5a03e0760884f6caf356c..3fa9504f3e74d164234a63879458caeaa8a38767 100644 (file)
@@ -165,9 +165,6 @@ class HaRouter(router.RouterInfo):
     def get_router_cidrs(self, device):
         return set(self._ha_get_existing_cidrs(device.name))
 
-    def _ha_external_gateway_removed(self, interface_name):
-        self._clear_vips(interface_name)
-
     def routes_updated(self):
         new_routes = self.router['routes']
 
@@ -216,7 +213,7 @@ class HaRouter(router.RouterInfo):
                 return False
         return True
 
-    def _ha_disable_addressing_on_interface(self, interface_name):
+    def _disable_ipv6_addressing_on_interface(self, interface_name):
         """Disable IPv6 link local addressing on the device and add it as
         a VIP to keepalived. This means that the IPv6 link local address
         will only be present on the master.
@@ -230,15 +227,10 @@ class HaRouter(router.RouterInfo):
         self._remove_vip(ipv6_lladdr)
         self._add_vip(ipv6_lladdr, interface_name, scope='link')
 
-    def _ha_external_gateway_added(self, ex_gw_port, interface_name):
+    def _add_gateway_vip(self, ex_gw_port, interface_name):
         self._add_vip(ex_gw_port['ip_cidr'], interface_name)
         self._add_default_gw_virtual_route(ex_gw_port, interface_name)
 
-    def _ha_external_gateway_updated(self, ex_gw_port, interface_name):
-        old_gateway_cidr = self.ex_gw_port['ip_cidr']
-        self._remove_vip(old_gateway_cidr)
-        self._ha_external_gateway_added(ex_gw_port, interface_name)
-
     def add_floating_ip(self, fip, interface_name, device):
         fip_ip = fip['floating_ip_address']
         ip_cidr = common_utils.ip_to_cidr(fip_ip)
@@ -261,7 +253,7 @@ class HaRouter(router.RouterInfo):
                              namespace=self.ns_name,
                              prefix=router.INTERNAL_DEV_PREFIX)
 
-        self._ha_disable_addressing_on_interface(interface_name)
+        self._disable_ipv6_addressing_on_interface(interface_name)
         self._add_vip(port['ip_cidr'], interface_name)
 
     def internal_network_removed(self, port):
@@ -319,3 +311,20 @@ class HaRouter(router.RouterInfo):
         state = 'master' if ha_cidr in cidrs else 'backup'
         self.ha_state = state
         callback(self.router_id, state)
+
+    def external_gateway_added(self, ex_gw_port, interface_name):
+        self._plug_external_gateway(ex_gw_port, interface_name, self.ns_name)
+        self._add_gateway_vip(ex_gw_port, interface_name)
+        self._disable_ipv6_addressing_on_interface(interface_name)
+
+    def external_gateway_updated(self, ex_gw_port, interface_name):
+        self._plug_external_gateway(ex_gw_port, interface_name, self.ns_name)
+        old_gateway_cidr = self.ex_gw_port['ip_cidr']
+        self._remove_vip(old_gateway_cidr)
+        self._add_gateway_vip(ex_gw_port, interface_name)
+
+    def external_gateway_removed(self, ex_gw_port, interface_name):
+        self._clear_vips(interface_name)
+
+        super(HaRouter, self).external_gateway_removed(ex_gw_port,
+                                                       interface_name)
index 43cd6ef83ac52e8e9ced373edd5ca1b946a6e419..43ec8ae86081a88a2bac7eea02c1cc0c43a03568 100644 (file)
@@ -26,6 +26,7 @@ from neutron.i18n import _LE, _LW
 
 LOG = logging.getLogger(__name__)
 INTERNAL_DEV_PREFIX = namespaces.INTERNAL_DEV_PREFIX
+EXTERNAL_DEV_PREFIX = namespaces.EXTERNAL_DEV_PREFIX
 
 
 class RouterInfo(object):
@@ -88,6 +89,12 @@ class RouterInfo(object):
     def get_internal_device_name(self, port_id):
         return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
 
+    def get_external_device_name(self, port_id):
+        return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
+
+    def get_external_device_interface_name(self, ex_gw_port):
+        return self.get_external_device_name(ex_gw_port['id'])
+
     def _set_subnet_info(self, port):
         ips = port['fixed_ips']
         if not ips:
@@ -101,8 +108,9 @@ class RouterInfo(object):
     def perform_snat_action(self, snat_callback, *args):
         # Process SNAT rules for attached subnets
         if self._snat_action:
-            snat_callback(self, self._router.get('gw_port'),
-                          *args, action=self._snat_action)
+            snat_callback(self._router.get('gw_port'),
+                          *args,
+                          action=self._snat_action)
         self._snat_action = None
 
     def _update_routing_table(self, operation, route):
@@ -338,8 +346,7 @@ class RouterInfo(object):
 
         existing_devices = self._get_existing_devices()
         current_internal_devs = set(n for n in existing_devices
-                                    if n.startswith(
-                                        INTERNAL_DEV_PREFIX))
+                                    if n.startswith(INTERNAL_DEV_PREFIX))
         current_port_devs = set(self.get_internal_device_name(port_id)
                                 for port_id in current_port_ids)
         stale_devs = current_internal_devs - current_port_devs
@@ -349,3 +356,163 @@ class RouterInfo(object):
             self.driver.unplug(stale_dev,
                                namespace=self.ns_name,
                                prefix=INTERNAL_DEV_PREFIX)
+
+    def _list_floating_ip_cidrs(self):
+        # 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 = self.get_floating_ips()
+        return [common_utils.ip_to_cidr(ip['floating_ip_address'])
+                for ip in floating_ips]
+
+    def _plug_external_gateway(self, ex_gw_port, interface_name, ns_name):
+        if not ip_lib.device_exists(interface_name, 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=EXTERNAL_DEV_PREFIX)
+
+    def _external_gateway_added(self, ex_gw_port, interface_name,
+                                ns_name, preserve_ips):
+        self._plug_external_gateway(ex_gw_port, interface_name, ns_name)
+
+        self.driver.init_l3(interface_name,
+                            [ex_gw_port['ip_cidr']],
+                            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]
+        ip_lib.send_gratuitous_arp(ns_name,
+                                   interface_name,
+                                   ip_address,
+                                   self.agent_conf.send_arp_for_ha)
+
+    def external_gateway_added(self, ex_gw_port, interface_name):
+        preserve_ips = self._list_floating_ip_cidrs()
+        self._external_gateway_added(
+            ex_gw_port, interface_name, self.ns_name, preserve_ips)
+
+    def external_gateway_updated(self, ex_gw_port, interface_name):
+        preserve_ips = self._list_floating_ip_cidrs()
+        self._external_gateway_added(
+            ex_gw_port, interface_name, self.ns_name, preserve_ips)
+
+    def external_gateway_removed(self, ex_gw_port, interface_name):
+        self.driver.unplug(interface_name,
+                           bridge=self.agent_conf.external_network_bridge,
+                           namespace=self.ns_name,
+                           prefix=EXTERNAL_DEV_PREFIX)
+
+    def _process_external_gateway(self, ex_gw_port):
+        # TODO(Carl) Refactor to clarify roles of ex_gw_port vs self.ex_gw_port
+        ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or
+                         self.ex_gw_port and self.ex_gw_port['id'])
+
+        interface_name = None
+        if ex_gw_port_id:
+            interface_name = self.get_external_device_name(ex_gw_port_id)
+        if ex_gw_port:
+            def _gateway_ports_equal(port1, port2):
+                def _get_filtered_dict(d, ignore):
+                    return dict((k, v) for k, v in d.iteritems()
+                                if k not in ignore)
+
+                keys_to_ignore = set(['binding:host_id'])
+                port1_filtered = _get_filtered_dict(port1, keys_to_ignore)
+                port2_filtered = _get_filtered_dict(port2, keys_to_ignore)
+                return port1_filtered == port2_filtered
+
+            self._set_subnet_info(ex_gw_port)
+            if not self.ex_gw_port:
+                self.external_gateway_added(ex_gw_port, interface_name)
+            elif not _gateway_ports_equal(ex_gw_port, self.ex_gw_port):
+                self.external_gateway_updated(ex_gw_port, interface_name)
+        elif not ex_gw_port and self.ex_gw_port:
+            self.external_gateway_removed(self.ex_gw_port, interface_name)
+
+        existing_devices = self._get_existing_devices()
+        stale_devs = [dev for dev in existing_devices
+                      if dev.startswith(EXTERNAL_DEV_PREFIX)
+                      and dev != interface_name]
+        for stale_dev in stale_devs:
+            LOG.debug('Deleting stale external router device: %s', stale_dev)
+            self.driver.unplug(stale_dev,
+                               bridge=self.agent_conf.external_network_bridge,
+                               namespace=self.ns_name,
+                               prefix=EXTERNAL_DEV_PREFIX)
+
+        # Process SNAT rules for external gateway
+        self.perform_snat_action(self._handle_router_snat_rules,
+                                 interface_name)
+
+    def external_gateway_nat_rules(self, ex_gw_ip, interface_name):
+        rules = [('POSTROUTING', '! -i %(interface_name)s '
+                  '! -o %(interface_name)s -m conntrack ! '
+                  '--ctstate DNAT -j ACCEPT' %
+                  {'interface_name': interface_name}),
+                 ('snat', '-o %s -j SNAT --to-source %s' %
+                  (interface_name, ex_gw_ip))]
+        return rules
+
+    def _empty_snat_chains(self, iptables_manager):
+        iptables_manager.ipv4['nat'].empty_chain('POSTROUTING')
+        iptables_manager.ipv4['nat'].empty_chain('snat')
+
+    def _add_snat_rules(self, ex_gw_port, iptables_manager,
+                        interface_name, action):
+        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
+            for ip_addr in ex_gw_port['fixed_ips']:
+                ex_gw_ip = ip_addr['ip_address']
+                if netaddr.IPAddress(ex_gw_ip).version == 4:
+                    rules = self.external_gateway_nat_rules(ex_gw_ip,
+                                                            interface_name)
+                    for rule in rules:
+                        iptables_manager.ipv4['nat'].add_rule(*rule)
+                    break
+
+    def _handle_router_snat_rules(self, ex_gw_port,
+                                  interface_name, action):
+        self._empty_snat_chains(self.iptables_manager)
+
+        self.iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat')
+
+        self._add_snat_rules(ex_gw_port,
+                             self.iptables_manager,
+                             interface_name,
+                             action)
+
+    def process_external(self, agent):
+        existing_floating_ips = self.floating_ips
+        try:
+            with self.iptables_manager.defer_apply():
+                ex_gw_port = self.get_ex_gw_port()
+                self._process_external_gateway(ex_gw_port)
+                # TODO(Carl) Return after setting existing_floating_ips and
+                # still call update_fip_statuses?
+                if not ex_gw_port:
+                    return
+
+                # Process SNAT/DNAT rules and addresses for floating IPs
+                if self.router['distributed']:
+                    self.create_dvr_fip_interfaces(ex_gw_port)
+                self.process_snat_dnat_for_fip()
+
+            # Once NAT rules for floating IPs are safely in place
+            # configure their addresses on the external gateway port
+            interface_name = self.get_external_device_interface_name(
+                ex_gw_port)
+            fip_statuses = self.configure_fip_addresses(interface_name)
+
+        except (n_exc.FloatingIpSetupException,
+                n_exc.IpTablesApplyException) as e:
+                # All floating IPs must be put in error state
+                LOG.exception(e)
+                fip_statuses = self.put_fips_in_error_state()
+
+        agent.update_fip_statuses(self, existing_floating_ips, fip_statuses)
index 01eef6469e441cd3416d822e4c268ee384c163dd..b0147bd9b9f1f316988a428e60fab96f61d30eb5 100755 (executable)
@@ -157,7 +157,7 @@ class L3AgentTestFramework(base.BaseOVSLinuxTestCase):
         external_port = router.get_ex_gw_port()
         ex_port_ipv6 = router._get_ipv6_lladdr(
             external_port['mac_address'])
-        external_device_name = self.agent.get_external_device_name(
+        external_device_name = router.get_external_device_name(
             external_port['id'])
         external_device_cidr = external_port['ip_cidr']
         internal_port = router.router[l3_constants.INTERFACE_KEY][0]
@@ -270,7 +270,7 @@ class L3AgentTestFramework(base.BaseOVSLinuxTestCase):
         floating_ips = router.router[l3_constants.FLOATINGIP_KEY]
         external_port = router.get_ex_gw_port()
         return len(floating_ips) and all(ip_lib.device_exists_with_ip_mac(
-            self.agent.get_external_device_name(external_port['id']),
+            router.get_external_device_name(external_port['id']),
             '%s/32' % fip['floating_ip_address'],
             external_port['mac_address'],
             namespace=router.ns_name) for fip in floating_ips)
@@ -433,7 +433,7 @@ class L3AgentTestCase(L3AgentTestFramework):
         self.assertNotIn(old_gw, new_config)
         self.assertIn(new_gw, new_config)
         external_port = router.get_ex_gw_port()
-        external_device_name = self.agent.get_external_device_name(
+        external_device_name = router.get_external_device_name(
             external_port['id'])
         self.assertNotIn('%s/24 dev %s' %
                          (old_external_device_ip, external_device_name),
@@ -448,7 +448,7 @@ class L3AgentTestCase(L3AgentTestFramework):
 
         if enable_ha:
             port = router.get_ex_gw_port()
-            interface_name = self.agent.get_external_device_name(port['id'])
+            interface_name = router.get_external_device_name(port['id'])
             self._assert_no_ip_addresses_on_interface(router.ns_name,
                                                       interface_name)
             utils.wait_until_true(lambda: router.ha_state == 'master')
@@ -496,12 +496,12 @@ class L3AgentTestCase(L3AgentTestFramework):
     def _assert_external_device(self, router):
         external_port = router.get_ex_gw_port()
         self.assertTrue(self.device_exists_with_ip_mac(
-            external_port, self.agent.get_external_device_name,
+            external_port, router.get_external_device_name,
             router.ns_name))
 
     def _assert_gateway(self, router):
         external_port = router.get_ex_gw_port()
-        external_device_name = self.agent.get_external_device_name(
+        external_device_name = router.get_external_device_name(
             external_port['id'])
         external_device = ip_lib.IPDevice(external_device_name,
                                           namespace=router.ns_name)
@@ -797,7 +797,7 @@ class TestDvrRouter(L3AgentTestFramework):
         # snat_ns_name namespace
         if self.agent.conf.agent_mode == 'dvr_snat':
             self.assertTrue(self.device_exists_with_ip_mac(
-                external_port, self.agent.get_external_device_name,
+                external_port, router.get_external_device_name,
                 snat_ns_name))
         # if the agent is in dvr mode then the snat_ns_name namespace
         # should not be present at all:
@@ -829,7 +829,7 @@ class TestDvrRouter(L3AgentTestFramework):
         namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name(
             router.router_id)
         external_port = router.get_ex_gw_port()
-        external_device_name = self.agent.get_external_device_name(
+        external_device_name = router.get_external_device_name(
             external_port['id'])
         external_device = ip_lib.IPDevice(external_device_name,
                                           namespace=namespace)
index a88be818342f109d992d3f39905a14c08ccafc49..a34ee482a8501135c9545bd3b0f19bf3e26804e9 100644 (file)
@@ -412,6 +412,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             ri._set_subnet_info = mock.Mock()
             ri._set_subnet_arp_info = mock.Mock()
             ri._internal_network_added = mock.Mock()
+            ri._set_subnet_arp_info = mock.Mock()
             ri.internal_network_added(port)
             self.assertEqual(ri._snat_redirect_add.call_count, 1)
             self.assertEqual(ri._set_subnet_info.call_count, 1)
@@ -455,12 +456,12 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         if router.get('distributed'):
             agent.conf.agent_mode = 'dvr_snat'
             agent.host = HOSTNAME
-            agent._create_dvr_gateway = mock.Mock()
             ri = dvr_router.DvrRouter(agent,
                                       HOSTNAME,
                                       router['id'],
                                       router,
                                       **self.ri_kwargs)
+            ri._create_dvr_gateway = mock.Mock()
             ri.get_snat_interfaces = mock.Mock(return_value=self.snat_ports)
             ri.create_snat_namespace()
             ri.fip_ns = agent.get_fip_ns(ex_net_id)
@@ -478,7 +479,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
                       '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'])
+        interface_name = ri.get_external_device_name(ex_gw_port['id'])
 
         if action == 'add':
             self.device_exists.return_value = False
@@ -487,7 +488,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
                                          '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)
+            ri.external_gateway_added(ex_gw_port, interface_name)
             if not router.get('distributed'):
                 self.assertEqual(self.mock_driver.plug.call_count, 1)
                 self.assertEqual(self.mock_driver.init_l3.call_count, 1)
@@ -502,15 +503,15 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
                                                             ['20.0.0.30/24'],
                                                             **kwargs)
             else:
-                agent._create_dvr_gateway.assert_called_once_with(
-                    ri, ex_gw_port, interface_name,
+                ri._create_dvr_gateway.assert_called_once_with(
+                    ex_gw_port, interface_name,
                     self.snat_ports)
 
         elif action == 'remove':
             self.device_exists.return_value = True
-            agent._map_internal_interfaces = mock.Mock(return_value=sn_port)
+            ri._map_internal_interfaces = mock.Mock(return_value=sn_port)
             ri._snat_redirect_remove = mock.Mock()
-            agent.external_gateway_removed(ri, ex_gw_port, interface_name)
+            ri.external_gateway_removed(ex_gw_port, interface_name)
             if not router.get('distributed'):
                 self.mock_driver.unplug.assert_called_once_with(
                     interface_name,
@@ -525,7 +526,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         else:
             raise Exception("Invalid action %s" % action)
 
-    def _prepare_ext_gw_test(self, agent):
+    def _prepare_ext_gw_test(self, ri):
         ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
                                      'subnet_id': _uuid()}],
                       'subnet': {'gateway_ip': '20.0.0.1'},
@@ -534,7 +535,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
                       'network_id': _uuid(),
                       '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'])
+        interface_name = ri.get_external_device_name(ex_gw_port['id'])
 
         self.device_exists.return_value = True
 
@@ -542,17 +543,15 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
 
     def test_external_gateway_updated(self):
         router = prepare_router_data(num_internal_ports=2)
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        interface_name, ex_gw_port = self._prepare_ext_gw_test(agent)
+        interface_name, ex_gw_port = self._prepare_ext_gw_test(ri)
 
         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_updated(ri, ex_gw_port,
-                                     interface_name)
+        ri.external_gateway_updated(ex_gw_port, interface_name)
         self.assertEqual(self.mock_driver.plug.call_count, 0)
         self.assertEqual(self.mock_driver.init_l3.call_count, 1)
         self.send_arp.assert_called_once_with(ri.ns_name, interface_name,
@@ -575,19 +574,17 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
                                   router,
                                   **self.ri_kwargs)
         ri.create_snat_namespace()
-        interface_name, ex_gw_port = self._prepare_ext_gw_test(agent)
-        agent._external_gateway_added = mock.Mock()
+        interface_name, ex_gw_port = self._prepare_ext_gw_test(ri)
+        ri._external_gateway_added = mock.Mock()
 
         # test agent mode = dvr (compute node)
-        router['distributed'] = True
         router['gw_port_host'] = host
         agent.conf.agent_mode = agent_mode
 
-        agent.external_gateway_updated(ri, ex_gw_port,
-                                       interface_name)
+        ri.external_gateway_updated(ex_gw_port, interface_name)
         # no gateway should be added on dvr node
         self.assertEqual(expected_call_count,
-                         agent._external_gateway_added.call_count)
+                         ri._external_gateway_added.call_count)
 
     def test_ext_gw_updated_dvr_agent_mode(self):
         # no gateway should be added on dvr node
@@ -599,7 +596,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
 
     def test_ext_gw_updated_dvr_snat_agent_mode_host(self):
         # gateway should be added on dvr_snat node
-        self._test_ext_gw_updated_dvr_agent_mode(self.conf.host,
+        self._test_ext_gw_updated_dvr_agent_mode(HOSTNAME,
                                                  'dvr_snat', 1)
 
     def test_agent_add_external_gateway(self):
@@ -772,13 +769,13 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         router = ri.router
         agent.host = HOSTNAME
         fake_fip_id = 'fake_fip_id'
-        agent.create_dvr_fip_interfaces = mock.Mock()
+        ri.create_dvr_fip_interfaces = mock.Mock()
         ri.process_floating_ip_addresses = mock.Mock()
         ri.process_floating_ip_nat_rules = mock.Mock()
         ri.process_floating_ip_addresses.return_value = {
             fake_fip_id: 'ACTIVE'}
-        agent.external_gateway_added = mock.Mock()
-        agent.external_gateway_updated = mock.Mock()
+        ri.external_gateway_added = mock.Mock()
+        ri.external_gateway_updated = mock.Mock()
         fake_floatingips1 = {'floatingips': [
             {'id': fake_fip_id,
              'floating_ip_address': '8.8.8.8',
@@ -790,7 +787,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         ri.process_floating_ip_addresses.reset_mock()
         ri.process_floating_ip_nat_rules.assert_called_with()
         ri.process_floating_ip_nat_rules.reset_mock()
-        agent.external_gateway_added.reset_mock()
+        ri.external_gateway_added.reset_mock()
 
         # remap floating IP to a new fixed ip
         fake_floatingips2 = copy.deepcopy(fake_floatingips1)
@@ -802,10 +799,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         ri.process_floating_ip_addresses.reset_mock()
         ri.process_floating_ip_nat_rules.assert_called_with()
         ri.process_floating_ip_nat_rules.reset_mock()
-        self.assertEqual(agent.external_gateway_added.call_count, 0)
-        self.assertEqual(agent.external_gateway_updated.call_count, 0)
-        agent.external_gateway_added.reset_mock()
-        agent.external_gateway_updated.reset_mock()
+        self.assertEqual(ri.external_gateway_added.call_count, 0)
+        self.assertEqual(ri.external_gateway_updated.call_count, 0)
+        ri.external_gateway_added.reset_mock()
+        ri.external_gateway_updated.reset_mock()
 
         # change the ex_gw_port a bit to test gateway update
         new_gw_port = copy.deepcopy(ri.router['gw_port'])
@@ -817,8 +814,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent.process_router(ri)
         ri.process_floating_ip_addresses.reset_mock()
         ri.process_floating_ip_nat_rules.reset_mock()
-        self.assertEqual(agent.external_gateway_added.call_count, 0)
-        self.assertEqual(agent.external_gateway_updated.call_count, 1)
+        self.assertEqual(ri.external_gateway_added.call_count, 0)
+        self.assertEqual(ri.external_gateway_updated.call_count, 1)
 
         # remove just the floating ips
         del router[l3_constants.FLOATINGIP_KEY]
@@ -853,7 +850,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         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)
+                ri.create_dvr_fip_interfaces(ex_gw_port)
             fip_statuses = ri.process_floating_ip_addresses(
                 mock.sentinel.interface_name)
         self.assertEqual({fip_id: l3_constants.FLOATINGIP_STATUS_ACTIVE},
@@ -929,7 +926,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
                                      sub_info):
             fips.return_value = fake_floatingips
             fip_gw_port.return_value = agent_gateway_port[0]
-            agent.create_dvr_fip_interfaces(ri, ext_gw_port)
+            ri.create_dvr_fip_interfaces(ext_gw_port)
             self.assertTrue(fip_gw_port.called)
             self.assertTrue(fips.called)
             self.assertEqual(ri.fip_ns.agent_gateway_port,
@@ -949,7 +946,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
         ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
-        agent.get_external_device_name = mock.Mock(return_value='exgw')
+        ri.get_external_device_name = mock.Mock(return_value='exgw')
         self._test_process_floating_ip_addresses_add(ri, agent)
 
     def test_process_router_dist_floating_ip_add(self):
@@ -993,7 +990,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router = prepare_router_data(enable_snat=True)
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        agent.external_gateway_added = mock.Mock()
+        ri.external_gateway_added = mock.Mock()
         # Process with NAT
         agent.process_router(ri)
         orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
@@ -1014,7 +1011,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router = prepare_router_data(enable_snat=False)
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        agent.external_gateway_added = mock.Mock()
+        ri.external_gateway_added = mock.Mock()
         # Process without NAT
         agent.process_router(ri)
         orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
@@ -1035,7 +1032,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router = prepare_router_data()
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        agent.external_gateway_added = mock.Mock()
+        ri.external_gateway_added = mock.Mock()
         # Process with NAT
         agent.process_router(ri)
         # Add an interface and reprocess
@@ -1053,7 +1050,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         gw_port = router['gw_port']
         router['gw_port'] = None
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        agent.external_gateway_added = mock.Mock()
+        ri.external_gateway_added = mock.Mock()
         self._process_router_instance_for_agent(agent, ri, router)
         orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
 
@@ -1061,7 +1058,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         router['gw_port'] = gw_port
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
         with mock.patch.object(
-                agent,
+                ri,
                 'external_gateway_nat_rules') as external_gateway_nat_rules:
             self._process_router_instance_for_agent(agent, ri, router)
             new_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
@@ -1074,7 +1071,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             self, router, ra_mode=None, addr_mode=None):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        agent.external_gateway_added = mock.Mock()
+        ri.external_gateway_added = mock.Mock()
         # Process with NAT
         agent.process_router(ri)
         orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
@@ -1132,7 +1129,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router = prepare_router_data()
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        agent.external_gateway_added = mock.Mock()
+        ri.external_gateway_added = mock.Mock()
         # Process with NAT
         agent.process_router(ri)
         # Add an IPv4 and IPv6 interface and reprocess
@@ -1146,7 +1143,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router = prepare_router_data(num_internal_ports=2)
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        agent.external_gateway_added = mock.Mock()
+        ri.external_gateway_added = mock.Mock()
         # Process with NAT
         agent.process_router(ri)
         # Add an interface and reprocess
@@ -1161,7 +1158,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router = prepare_router_data()
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        agent.external_gateway_added = mock.Mock()
+        ri.external_gateway_added = mock.Mock()
         self._process_router_instance_for_agent(agent, ri, router)
         # Add an IPv6 interface and reprocess
         router_append_interface(router, count=1, ip_version=6)
@@ -1179,7 +1176,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router = prepare_router_data()
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        agent.external_gateway_added = mock.Mock()
+        ri.external_gateway_added = mock.Mock()
         with mock.patch.object(
                 ri,
                 'internal_network_added') as internal_network_added:
@@ -1204,7 +1201,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router = prepare_router_data()
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        agent.external_gateway_added = mock.Mock()
+        ri.external_gateway_added = mock.Mock()
         # add an internal port
         agent.process_router(ri)
 
@@ -1246,7 +1243,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             ri = legacy_router.LegacyRouter(router['id'],
                                             router,
                                             **self.ri_kwargs)
-            agent.external_gateway_added = mock.Mock()
+            ri.external_gateway_added = mock.Mock()
             agent.process_router(ri)
             # Assess the call for putting the floating IP up was performed
             mock_update_fip_status.assert_called_once_with(
@@ -1278,7 +1275,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
             ri.process_floating_ip_addresses = mock.Mock(
                 side_effect=RuntimeError)
-            agent.external_gateway_added = mock.Mock()
+            ri.external_gateway_added = mock.Mock()
             agent.process_router(ri)
             # Assess the call for putting the floating IP into Error
             # was performed
@@ -1301,7 +1298,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
 
             ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
             ri.iptables_manager._apply = mock.Mock(side_effect=Exception)
-            agent._process_external(ri)
+            ri.process_external(agent)
             # Assess the call for putting the floating IP into Error
             # was performed
             mock_update_fip_status.assert_called_once_with(
@@ -1316,24 +1313,22 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             agent,
             HOSTNAME,
             'foo_router_id',
-            {'distributed': True},
+            {},
             **self.ri_kwargs)
         ri.iptables_manager = mock.Mock()
 
-        with mock.patch.object(l3_agent.LOG, 'debug') as log_debug:
-            agent._handle_router_snat_rules(
-                ri, mock.ANY, mock.ANY, mock.ANY)
+        with mock.patch.object(dvr_router.LOG, 'debug') as log_debug:
+            ri._handle_router_snat_rules(mock.ANY, mock.ANY, mock.ANY)
         self.assertIsNone(ri.snat_iptables_manager)
         self.assertFalse(ri.iptables_manager.called)
         self.assertTrue(log_debug.called)
 
     def test_handle_router_snat_rules_add_back_jump(self):
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        ri = mock.MagicMock()
+        ri = l3router.RouterInfo(_uuid(), {}, **self.ri_kwargs)
+        ri.iptables_manager = 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")
+        ri._handle_router_snat_rules(port, "iface", "add_rules")
 
         nat = ri.iptables_manager.ipv4['nat']
         nat.empty_chain.assert_any_call('snat')
@@ -1346,12 +1341,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
                 break
 
     def test_handle_router_snat_rules_add_rules(self):
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         ri = l3router.RouterInfo(_uuid(), {}, **self.ri_kwargs)
         ex_gw_port = {'fixed_ips': [{'ip_address': '192.168.1.4'}]}
         ri.router = {'distributed': False}
-        agent._handle_router_snat_rules(ri, ex_gw_port,
-                                        "iface", "add_rules")
+        ri._handle_router_snat_rules(ex_gw_port, "iface", "add_rules")
 
         nat_rules = map(str, ri.iptables_manager.ipv4['nat'].rules)
         wrap_name = ri.iptables_manager.wrap_name
@@ -1388,9 +1381,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
                                                  'internal_network_removed'),
                                mock.patch.object(ri,
                                                  'internal_network_added'),
-                               mock.patch.object(l3_agent.L3NATAgent,
+                               mock.patch.object(ri,
                                                  'external_gateway_removed'),
-                               mock.patch.object(l3_agent.L3NATAgent,
+                               mock.patch.object(ri,
                                                  'external_gateway_added')
                                ) as (internal_network_removed,
                                      internal_network_added,
@@ -1769,8 +1762,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         interface_name = ri.get_snat_int_device_name(port_id)
         self.device_exists.return_value = False
 
-        agent._create_dvr_gateway(ri, dvr_gw_port, interface_name,
-                                  self.snat_ports)
+        ri._create_dvr_gateway(dvr_gw_port, interface_name, self.snat_ports)
 
         # check 2 internal ports are plugged
         # check 1 ext-gw-port is plugged
@@ -1818,7 +1810,6 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         agent.conf.agent_mode = 'dvr_snat'
         router = prepare_router_data(num_internal_ports=2)
-        router['distributed'] = True
         router['gw_port_host'] = HOSTNAME
         self.mock_driver.unplug.reset_mock()
 
@@ -1858,9 +1849,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             self.device_exists.return_value = True
             fip_ns = ri.fip_ns
 
-        agent.external_gateway_removed(
-            ri, ri.ex_gw_port,
-            agent.get_external_device_name(ri.ex_gw_port['id']))
+        ri.snat_namespace = mock.Mock()
+        ri.external_gateway_removed(
+            ri.ex_gw_port,
+            ri.get_external_device_name(ri.ex_gw_port['id']))
 
         if fip_ns:
             ri.remove_floating_ip.assert_called_once_with(self.mock_ip_dev,