]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Refactor management of namespaces in the L3 Agent
authorMiguel Lavalle <miguel@mlavalle.com>
Fri, 16 Jan 2015 04:16:01 +0000 (22:16 -0600)
committerCarl Baldwin <carl.baldwin@hp.com>
Wed, 4 Mar 2015 21:28:23 +0000 (21:28 +0000)
Creates classes representing the 3 types of namespaces handled by the L3 agent:
router, snat and fip.

The scope of this change is:

- Creation and deletion methods are provided for each namespace class
- Creation and deletion of router and snat namespaces are moved to the router
  classes. These namespaces are now members of the corresponding router class
- Invocation of Fip namespace creation and deletion is left in the agent, since
  the agent owns it
- A context manager is provided to move the namespaces (router and snat)
  cleanup code out of the agent

A follow up patchset will add methods to create and delete interfaces in the
namespaces. These methods are intended to be used by the router classes

Change-Id: I54b14e593ded6b2990d57a3ae9d598a699ae133e
Partially-Implements: bp restructure-l3-agent

18 files changed:
neutron/agent/l3/agent.py
neutron/agent/l3/dvr.py
neutron/agent/l3/dvr_fip_ns.py
neutron/agent/l3/dvr_router.py
neutron/agent/l3/dvr_snat_ns.py [new file with mode: 0644]
neutron/agent/l3/namespace_manager.py [new file with mode: 0644]
neutron/agent/l3/namespaces.py [new file with mode: 0644]
neutron/agent/l3/router_info.py
neutron/common/exceptions.py
neutron/tests/common/agents/l3_agent.py [deleted file]
neutron/tests/functional/agent/test_l3_agent.py
neutron/tests/unit/agent/l3/test_dvr_router.py
neutron/tests/unit/agent/l3/test_ha_router.py
neutron/tests/unit/agent/l3/test_l3_router.py
neutron/tests/unit/agent/l3/test_legacy_router.py
neutron/tests/unit/agent/l3/test_router_info.py
neutron/tests/unit/agent/test_dvr_fip_ns.py
neutron/tests/unit/test_l3_agent.py

index 2efbf4b94b255acbd452ad0e2c40fba84c043c0a..5abcf88c225f72cf96068793c7be792453d0341b 100644 (file)
@@ -22,12 +22,14 @@ from oslo_utils import importutils
 from oslo_utils import timeutils
 
 from neutron.agent.l3 import dvr
-from neutron.agent.l3 import dvr_fip_ns
 from neutron.agent.l3 import dvr_router
+from neutron.agent.l3 import dvr_snat_ns
 from neutron.agent.l3 import event_observers
 from neutron.agent.l3 import ha
 from neutron.agent.l3 import ha_router
 from neutron.agent.l3 import legacy_router
+from neutron.agent.l3 import namespace_manager
+from neutron.agent.l3 import namespaces
 from neutron.agent.l3 import router_processing_queue as queue
 from neutron.agent.linux import external_process
 from neutron.agent.linux import ip_lib
@@ -55,9 +57,10 @@ except Exception:
     from neutron.services.firewall.agents.l3reference import firewall_l3_agent
 
 LOG = logging.getLogger(__name__)
-NS_PREFIX = 'qrouter-'
-INTERNAL_DEV_PREFIX = 'qr-'
-EXTERNAL_DEV_PREFIX = 'qg-'
+# TODO(Carl) Following constants retained to increase SNR during refactoring
+NS_PREFIX = namespaces.NS_PREFIX
+INTERNAL_DEV_PREFIX = namespaces.INTERNAL_DEV_PREFIX
+EXTERNAL_DEV_PREFIX = namespaces.EXTERNAL_DEV_PREFIX
 
 
 class L3PluginApi(object):
@@ -197,7 +200,10 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
                         continue
             break
 
-        self._clean_stale_namespaces = self.conf.use_namespaces
+        self.namespaces_manager = namespace_manager.NamespaceManager(
+            self.conf,
+            self.driver,
+            self.conf.use_namespaces)
 
         self._queue = queue.RouterProcessingQueue()
         self.event_observers = event_observers.L3EventObservers()
@@ -226,95 +232,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
             LOG.error(msg)
             raise SystemExit(1)
 
-    def _list_namespaces(self):
-        """Get a set of all router namespaces on host
-
-        The argument routers is the list of routers that are recorded in
-        the database as being hosted on this node.
-        """
-        try:
-            root_ip = ip_lib.IPWrapper()
-
-            host_namespaces = root_ip.get_namespaces()
-            return set(ns for ns in host_namespaces
-                       if (ns.startswith(NS_PREFIX)
-                           or ns.startswith(dvr.SNAT_NS_PREFIX)))
-        except RuntimeError:
-            LOG.exception(_LE('RuntimeError in obtaining router list '
-                            'for namespace cleanup.'))
-            return set()
-
-    def _get_routers_namespaces(self, router_ids):
-        namespaces = set(self.get_ns_name(rid) for rid in router_ids)
-        namespaces.update(self.get_snat_ns_name(rid) for rid in router_ids)
-        return namespaces
-
-    def _cleanup_namespaces(self, router_namespaces, router_ids):
-        """Destroy stale router namespaces on host when L3 agent restarts
-
-        This routine is called when self._clean_stale_namespaces is True.
-
-        The argument router_namespaces is the list of all routers namespaces
-        The argument router_ids is the list of ids for known routers.
-        """
-        # Don't destroy namespaces of routers this agent handles.
-        ns_to_ignore = self._get_routers_namespaces(router_ids)
-
-        ns_to_destroy = router_namespaces - ns_to_ignore
-        for ns in ns_to_destroy:
-            try:
-                self._destroy_namespace(ns)
-            except RuntimeError:
-                LOG.exception(_LE('Failed to destroy stale router namespace '
-                                  '%s'), ns)
-        self._clean_stale_namespaces = False
-
-    def _destroy_namespace(self, ns):
-        if ns.startswith(NS_PREFIX):
-            self._destroy_router_namespace(ns)
-        elif ns.startswith(dvr_fip_ns.FIP_NS_PREFIX):
-            self._destroy_fip_namespace(ns)
-        elif ns.startswith(dvr.SNAT_NS_PREFIX):
-            self._destroy_snat_namespace(ns)
-
-    def _delete_namespace(self, ns_ip, ns):
-        try:
-            ns_ip.netns.delete(ns)
-        except RuntimeError:
-            LOG.exception(_LE('Failed trying to delete namespace: %s'), ns)
-
-    def _destroy_router_namespace(self, ns):
-        router_id = self.get_router_id(ns)
-        if router_id in self.router_info:
-            self.router_info[router_id].radvd.disable()
-        ns_ip = ip_lib.IPWrapper(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=ns,
-                                   prefix=INTERNAL_DEV_PREFIX)
-            elif d.name.startswith(dvr_fip_ns.ROUTER_2_FIP_DEV_PREFIX):
-                ns_ip.del_veth(d.name)
-            elif d.name.startswith(EXTERNAL_DEV_PREFIX):
-                self.driver.unplug(d.name,
-                                   bridge=self.conf.external_network_bridge,
-                                   namespace=ns,
-                                   prefix=EXTERNAL_DEV_PREFIX)
-
-        if self.conf.router_delete_namespaces:
-            self._delete_namespace(ns_ip, ns)
-
-    def _create_namespace(self, name):
-        ip_wrapper_root = ip_lib.IPWrapper()
-        ip_wrapper = ip_wrapper_root.ensure_namespace(name)
-        ip_wrapper.netns.execute(['sysctl', '-w', 'net.ipv4.ip_forward=1'])
-        if self.use_ipv6:
-            ip_wrapper.netns.execute(['sysctl', '-w',
-                                      'net.ipv6.conf.all.forwarding=1'])
-
-    def _create_router_namespace(self, ri):
-        self._create_namespace(ri.ns_name)
-
     def _fetch_external_net_id(self, force=False):
         """Find UUID of single external network for this agent."""
         if self.conf.gateway_external_network_id:
@@ -349,14 +266,11 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         if router.get('distributed') and router.get('ha'):
             raise n_exc.DvrHaRouterNotSupported(router_id=router_id)
 
-        ns_name = (self.get_ns_name(router_id)
-                   if self.conf.use_namespaces else None)
         args = []
         kwargs = {
             'router_id': router_id,
             'router': router,
             'use_ipv6': self.use_ipv6,
-            'ns_name': ns_name,
             'agent_conf': self.conf,
             'interface_driver': self.driver,
         }
@@ -380,8 +294,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
             adv_svc.AdvancedService.before_router_added, ri)
 
         self.router_info[router_id] = ri
-        if self.conf.use_namespaces:
-            self._create_router_namespace(ri)
+        ri.create()
         self.process_router_add(ri)
 
         if ri.is_ha:
@@ -405,8 +318,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         ri.router[l3_constants.FLOATINGIP_KEY] = []
         self.process_router(ri)
         del self.router_info[router_id]
-        self._destroy_router_namespace(ri.ns_name)
-
+        ri.delete()
         self.event_observers.notify(
             adv_svc.AdvancedService.after_router_removed, ri)
 
@@ -656,12 +568,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
     def get_external_device_name(self, port_id):
         return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
 
-    def get_ns_name(self, router_id):
-        return (NS_PREFIX + router_id)
-
-    def get_router_id(self, ns_name):
-        return ns_name[len(NS_PREFIX):]
-
     def get_floating_agent_gw_interface(self, ri, ext_net_id):
         """Filter Floating Agent GW port for the external network."""
         fip_ports = ri.router.get(l3_constants.FLOATINGIP_AGENT_INTF_KEY, [])
@@ -711,7 +617,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         if ri.router['distributed']:
             if (self.conf.agent_mode == l3_constants.L3_AGENT_MODE_DVR_SNAT and
                 self.get_gw_port_host(ri.router) == self.host):
-                ns_name = self.get_snat_ns_name(ri.router['id'])
+                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'])
@@ -764,7 +670,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
 
             if (self.conf.agent_mode == l3_constants.L3_AGENT_MODE_DVR_SNAT
                 and self.get_gw_port_host(ri.router) == self.host):
-                ns_name = self.get_snat_ns_name(ri.router['id'])
+                ns_name = ri.snat_namespace.name
             else:
                 # not hosting agent - no work to do
                 LOG.debug('DVR: CSNAT not hosted: %s', ex_gw_port)
@@ -780,7 +686,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
                            namespace=ns_name,
                            prefix=EXTERNAL_DEV_PREFIX)
         if ri.router['distributed']:
-            self._destroy_snat_namespace(ns_name)
+            ri.delete_snat_namespace()
 
     def external_gateway_nat_rules(self, ex_gw_ip, interface_name):
         rules = [('POSTROUTING', '! -i %(interface_name)s '
@@ -834,7 +740,8 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
                                         ['ip_address'], port, interface_name)
                 if (self.conf.agent_mode == l3_constants.L3_AGENT_MODE_DVR_SNAT
                     and self.get_gw_port_host(ri.router) == self.host):
-                    ns_name = self.get_snat_ns_name(ri.router['id'])
+                    ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(
+                        ri.router['id'])
                     self._set_subnet_info(sn_port)
                     interface_name = (
                           self.get_snat_int_device_name(sn_port['id']))
@@ -860,7 +767,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
                     snat_interface = (
                         self.get_snat_int_device_name(snat_port['id'])
                     )
-                    ns_name = self.get_snat_ns_name(ri.router['id'])
+                    ns_name = ri.snat_namespace.name
                     prefix = dvr.SNAT_INT_DEV_PREFIX
                     if ip_lib.device_exists(snat_interface,
                                             namespace=ns_name):
@@ -1013,11 +920,18 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
         # uncaught -- prevents setting it to False below then the next call
         # to periodic_sync_routers_task will re-enter this code and try again.
 
-        # Capture a picture of namespaces *before* fetching the full list from
-        # the database.  This is important to correctly identify stale ones.
-        namespaces = set()
-        if self._clean_stale_namespaces:
-            namespaces = self._list_namespaces()
+        # Context manager self.namespaces_manager captures a picture of
+        # namespaces *before* fetch_and_sync_all_routers fetches the full list
+        # of routers from the database.  This is important to correctly
+        # identify stale ones.
+
+        try:
+            with self.namespaces_manager as ns_manager:
+                self.fetch_and_sync_all_routers(context, ns_manager)
+        except n_exc.AbortSyncRouters:
+            self.fullsync = True
+
+    def fetch_and_sync_all_routers(self, context, ns_manager):
         prev_router_ids = set(self.router_info)
         timestamp = timeutils.utcnow()
 
@@ -1030,9 +944,11 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
 
         except oslo_messaging.MessagingException:
             LOG.exception(_LE("Failed synchronizing routers due to RPC error"))
+            raise n_exc.AbortSyncRouters()
         else:
             LOG.debug('Processing :%r', routers)
             for r in routers:
+                ns_manager.keep_router(r['id'])
                 update = queue.RouterUpdate(r['id'],
                                             queue.PRIORITY_SYNC_ROUTERS_TASK,
                                             router=r,
@@ -1041,24 +957,17 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
             self.fullsync = False
             LOG.debug("periodic_sync_routers_task successfully completed")
 
-            # Resync is not necessary for the cleanup of stale namespaces
             curr_router_ids = set([r['id'] for r in routers])
 
-            # Two kinds of stale routers:  Routers for which info is cached in
-            # self.router_info and the others.  First, handle the former.
+            # Delete routers that have disappeared since the last sync
             for router_id in prev_router_ids - curr_router_ids:
+                ns_manager.keep_router(router_id)
                 update = queue.RouterUpdate(router_id,
                                             queue.PRIORITY_SYNC_ROUTERS_TASK,
                                             timestamp=timestamp,
                                             action=queue.DELETE_ROUTER)
                 self._queue.add(update)
 
-            # Next, one effort to clean out namespaces for which we don't have
-            # a record.  (i.e. _clean_stale_namespaces=False after one pass)
-            if self._clean_stale_namespaces:
-                ids_to_keep = curr_router_ids | prev_router_ids
-                self._cleanup_namespaces(namespaces, ids_to_keep)
-
     def after_start(self):
         eventlet.spawn_n(self._process_routers_loop)
         LOG.info(_LI("L3 agent started"))
index fab6f05752721883dc35fae842164606ab3099d9..20066e0b94a625c2502a739909c3d033ce5ac1be 100644 (file)
@@ -17,6 +17,7 @@ import netaddr
 import weakref
 
 from neutron.agent.l3 import dvr_fip_ns
+from neutron.agent.l3 import dvr_snat_ns
 from neutron.agent.linux import ip_lib
 from neutron.agent.linux import iptables_manager
 from neutron.common import constants as l3_constants
@@ -25,8 +26,9 @@ from neutron.openstack.common import log as logging
 
 LOG = logging.getLogger(__name__)
 
-SNAT_INT_DEV_PREFIX = 'sg-'
-SNAT_NS_PREFIX = 'snat-'
+# TODO(Carl) Following constants retained to increase SNR during refactoring
+SNAT_INT_DEV_PREFIX = dvr_snat_ns.SNAT_INT_DEV_PREFIX
+SNAT_NS_PREFIX = dvr_snat_ns.SNAT_NS_PREFIX
 # xor-folding mask used for IPv6 rule index
 MASK_30 = 0x3fffffff
 
@@ -54,24 +56,10 @@ class AgentMixin(object):
 
         return fip_ns
 
-    def _destroy_snat_namespace(self, ns):
-        ns_ip = ip_lib.IPWrapper(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):
         ex_net_id = ns[len(dvr_fip_ns.FIP_NS_PREFIX):]
         fip_ns = self.get_fip_ns(ex_net_id)
-        fip_ns.destroy()
+        fip_ns.delete()
 
     def _set_subnet_arp_info(self, ri, port):
         """Set ARP info retrieved from Plugin for existing ports."""
@@ -102,6 +90,7 @@ class AgentMixin(object):
         return (SNAT_INT_DEV_PREFIX +
                 port_id)[:self.driver.DEV_NAME_LEN]
 
+    # TODO(Carl) Remove this method when vpnaas no longer needs it.
     def get_snat_ns_name(self, router_id):
         return (SNAT_NS_PREFIX + router_id)
 
@@ -150,21 +139,20 @@ class AgentMixin(object):
     def _create_dvr_gateway(self, ri, ex_gw_port, gw_interface_name,
                             snat_ports):
         """Create SNAT namespace."""
-        snat_ns_name = self.get_snat_ns_name(ri.router['id'])
-        self._create_namespace(snat_ns_name)
+        snat_ns = ri.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'],
+            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,
-                                     snat_ns_name, preserve_ips=[])
+                                     snat_ns.name, preserve_ips=[])
         ri.snat_iptables_manager = iptables_manager.IptablesManager(
-            namespace=snat_ns_name,
+            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)
index a8bd68014363e8ef36726a25380fc0931fe297f6..5369fc8b322a845db022f822ef662176faa16567 100644 (file)
@@ -16,10 +16,10 @@ import netaddr
 import os
 
 from neutron.agent.l3 import link_local_allocator as lla
+from neutron.agent.l3 import namespaces
 from neutron.agent.linux import ip_lib
 from neutron.agent.linux import iptables_manager
 from neutron.common import utils as common_utils
-from neutron.i18n import _LE
 from neutron.openstack.common import log as logging
 
 LOG = logging.getLogger(__name__)
@@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__)
 FIP_NS_PREFIX = 'fip-'
 FIP_EXT_DEV_PREFIX = 'fg-'
 FIP_2_ROUTER_DEV_PREFIX = 'fpr-'
-ROUTER_2_FIP_DEV_PREFIX = 'rfp-'
+ROUTER_2_FIP_DEV_PREFIX = namespaces.ROUTER_2_FIP_DEV_PREFIX
 # Route Table index for FIPs
 FIP_RT_TBL = 16
 FIP_LL_SUBNET = '169.254.30.0/23'
@@ -36,8 +36,13 @@ FIP_PR_START = 32768
 FIP_PR_END = FIP_PR_START + 40000
 
 
-class FipNamespace(object):
+class FipNamespace(namespaces.Namespace):
+
     def __init__(self, ext_net_id, agent_conf, driver, use_ipv6):
+        name = FIP_NS_PREFIX + ext_net_id
+        super(FipNamespace, self).__init__(
+            name, agent_conf, driver, use_ipv6)
+
         self._ext_net_id = ext_net_id
         self.agent_conf = agent_conf
         self.driver = driver
@@ -128,12 +133,9 @@ class FipNamespace(object):
                                                     '-j CT --notrack')
         self._iptables_manager.apply()
 
-    def destroy(self):
+    def delete(self):
         self.destroyed = True
-        ns = self.get_name()
-        # TODO(carl) Reconcile this with mlavelle's namespace work
-        # TODO(carl) mlavelle's work has self.ip_wrapper
-        ip_wrapper = ip_lib.IPWrapper(namespace=ns)
+        ip_wrapper = ip_lib.IPWrapper(namespace=self.name)
         for d in ip_wrapper.get_devices(exclude_loopback=True):
             if d.name.startswith(FIP_2_ROUTER_DEV_PREFIX):
                 # internal link between IRs and FIP NS
@@ -145,18 +147,14 @@ class FipNamespace(object):
                 ext_net_bridge = self.agent_conf.external_network_bridge
                 self.driver.unplug(d.name,
                                    bridge=ext_net_bridge,
-                                   namespace=ns,
+                                   namespace=self.name,
                                    prefix=FIP_EXT_DEV_PREFIX)
-        LOG.debug('DVR: destroy fip ns: %s', ns)
-        # TODO(mrsmith): add LOG warn if fip count != 0
-        if self.agent_conf.router_delete_namespaces:
-            try:
-                ip_wrapper.netns.delete(ns)
-            except RuntimeError:
-                LOG.exception(_LE('Failed trying to delete namespace: %s'), ns)
-
         self.agent_gateway_port = None
 
+        # TODO(mrsmith): add LOG warn if fip count != 0
+        LOG.debug('DVR: destroy fip ns: %s', self.name)
+        super(FipNamespace, self).delete()
+
     def create_gateway_port(self, agent_gateway_port):
         """Create Floating IP gateway port.
 
index b8532649507d07c2e4804868848e4438929a9c89..dfb30e7042e6bcce50e1d078a82e3599700d4da1 100644 (file)
@@ -13,6 +13,7 @@
 #    under the License.
 
 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.common import constants as l3_constants
@@ -30,6 +31,7 @@ class DvrRouter(router.RouterInfo):
         # Linklocal subnet for router and floating IP namespace link
         self.rtr_fip_subnet = None
         self.dist_fip_count = None
+        self.snat_namespace = None
 
     def get_floating_ips(self):
         """Filter Floating IPs to be hosted on this agent."""
@@ -121,7 +123,7 @@ class DvrRouter(router.RouterInfo):
                 # destroying it.  The two could end up conflicting on
                 # creating/destroying interfaces and such.  I think I'd like a
                 # semaphore to sync creation/deletion of this namespace.
-                self.fip_ns.destroy()
+                self.fip_ns.delete()
                 self.fip_ns = None
 
     def add_floating_ip(self, fip, interface_name, device):
@@ -137,3 +139,21 @@ class DvrRouter(router.RouterInfo):
     def remove_floating_ip(self, device, ip_cidr):
         super(DvrRouter, self).remove_floating_ip(device, ip_cidr)
         self.floating_ip_removed_dist(ip_cidr)
+
+    def create_snat_namespace(self):
+        # TODO(mlavalle): in the near future, this method should contain the
+        # code in the L3 agent that creates a gateway for a dvr. The first step
+        # is to move the creation of the snat namespace here
+        self.snat_namespace = dvr_snat_ns.SnatNamespace(self.router['id'],
+                                                        self.agent_conf,
+                                                        self.driver,
+                                                        self.use_ipv6)
+        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
diff --git a/neutron/agent/l3/dvr_snat_ns.py b/neutron/agent/l3/dvr_snat_ns.py
new file mode 100644 (file)
index 0000000..2ab940b
--- /dev/null
@@ -0,0 +1,44 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from neutron.agent.l3 import namespaces
+from neutron.agent.linux import ip_lib
+from neutron.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+SNAT_NS_PREFIX = 'snat-'
+SNAT_INT_DEV_PREFIX = 'sg-'
+
+
+class SnatNamespace(namespaces.Namespace):
+
+    def __init__(self, router_id, agent_conf, driver, use_ipv6):
+        self.router_id = router_id
+        name = self.get_snat_ns_name(router_id)
+        super(SnatNamespace, self).__init__(
+            name, agent_conf, driver, use_ipv6)
+
+    @classmethod
+    def get_snat_ns_name(cls, router_id):
+        return (SNAT_NS_PREFIX + router_id)
+
+    def delete(self):
+        ns_ip = ip_lib.IPWrapper(namespace=self.name)
+        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=self.name,
+                                   prefix=SNAT_INT_DEV_PREFIX)
+
+        # TODO(mrsmith): delete ext-gw-port
+        LOG.debug('DVR: destroy snat ns: %s', self.name)
+        super(SnatNamespace, self).delete()
diff --git a/neutron/agent/l3/namespace_manager.py b/neutron/agent/l3/namespace_manager.py
new file mode 100644 (file)
index 0000000..21f72cb
--- /dev/null
@@ -0,0 +1,120 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from neutron.agent.l3 import dvr_snat_ns
+from neutron.agent.l3 import namespaces
+from neutron.agent.linux import ip_lib
+from neutron.i18n import _LE
+from neutron.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+class NamespaceManager(object):
+
+    """Keeps track of namespaces that need to be cleaned up.
+
+    This is a context manager that looks to clean up stale namespaces that
+    have not been touched by the end of the "with" statement it is called
+    in.  This formalizes the pattern used in the L3 agent which enumerated
+    all of the namespaces known to the system before a full sync.  Then,
+    after the full sync completed, it cleaned up any that were not touched
+    during the sync. The agent and this context manager use method keep_router
+    to communicate. In the "with" statement, the agent calls keep_router to
+    record the id's of the routers whose namespaces should be preserved.
+    Any other router and snat namespace present in the system will be deleted
+    by the __exit__ method of this context manager
+
+    This pattern can be more generally applicable to other resources
+    besides namespaces in the future because it is idempotent and, as such,
+    does not rely on state recorded at runtime in the agent so it handles
+    agent restarts gracefully.
+    """
+
+    def __init__(self, agent_conf, driver, clean_stale):
+        """Initialize the NamespaceManager.
+
+        :param agent_conf: configuration from l3 agent
+        :param driver: to perform operations on devices
+        :param clean_stale: Whether to try to clean stale namespaces
+        """
+        self.agent_conf = agent_conf
+        self.driver = driver
+        self._clean_stale = clean_stale
+
+    def __enter__(self):
+        self._all_namespaces = set()
+        self._ids_to_keep = set()
+        if self._clean_stale:
+            self._all_namespaces = self.list_all()
+        return self
+
+    def __exit__(self, exc_type, value, traceback):
+        # TODO(carl) Preserves old behavior of L3 agent where cleaning
+        # namespaces was only done once after restart.  Still a good idea?
+        if exc_type:
+            # An exception occured in the caller's with statement
+            return False
+        if not self._clean_stale:
+            # No need to cleanup
+            return True
+        self._clean_stale = False
+
+        for ns in self._all_namespaces:
+            _ns_prefix, ns_id = self.get_prefix_and_id(ns)
+            if ns_id in self._ids_to_keep:
+                continue
+            if _ns_prefix == namespaces.NS_PREFIX:
+                ns = namespaces.RouterNamespace(ns_id,
+                                                self.agent_conf,
+                                                self.driver,
+                                                use_ipv6=False)
+            else:
+                ns = dvr_snat_ns.SnatNamespace(ns_id,
+                                               self.agent_conf,
+                                               self.driver,
+                                               use_ipv6=False)
+            try:
+                ns.delete()
+            except RuntimeError:
+                LOG.exception(_LE('Failed to destroy stale namespace %s'), ns)
+
+        return True
+
+    def keep_router(self, router_id):
+        self._ids_to_keep.add(router_id)
+
+    def get_prefix_and_id(self, ns_name):
+        """Get the prefix and id from the namespace name.
+
+        :param ns_name: The name of the namespace
+        :returns: tuple with prefix and id or None if no prefix matches
+        """
+        for prefix in [namespaces.NS_PREFIX, dvr_snat_ns.SNAT_NS_PREFIX]:
+            if ns_name.startswith(prefix):
+                return (prefix, ns_name[len(prefix):])
+
+    def is_managed(self, ns_name):
+        """Return True if the namespace name passed belongs to this manager."""
+        return self.get_prefix_and_id(ns_name) is not None
+
+    def list_all(self):
+        """Get a set of all namespaces on host managed by this manager."""
+        try:
+            root_ip = ip_lib.IPWrapper()
+
+            namespaces = root_ip.get_namespaces()
+            return set(ns for ns in namespaces if self.is_managed(ns))
+        except RuntimeError:
+            LOG.exception(_LE('RuntimeError in obtaining namespace list for '
+                              'namespace cleanup.'))
+            return set()
diff --git a/neutron/agent/l3/namespaces.py b/neutron/agent/l3/namespaces.py
new file mode 100644 (file)
index 0000000..27111ac
--- /dev/null
@@ -0,0 +1,83 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+#
+
+from neutron.agent.linux import ip_lib
+from neutron.i18n import _LE
+from neutron.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+NS_PREFIX = 'qrouter-'
+INTERNAL_DEV_PREFIX = 'qr-'
+EXTERNAL_DEV_PREFIX = 'qg-'
+# TODO(Carl) It is odd that this file needs this.  It is a dvr detail.
+ROUTER_2_FIP_DEV_PREFIX = 'rfp-'
+
+
+class Namespace(object):
+
+    def __init__(self, name, agent_conf, driver, use_ipv6):
+        self.name = name
+        self.ip_wrapper_root = ip_lib.IPWrapper()
+        self.agent_conf = agent_conf
+        self.driver = driver
+        self.use_ipv6 = use_ipv6
+
+    def create(self):
+        ip_wrapper = self.ip_wrapper_root.ensure_namespace(self.name)
+        cmd = ['sysctl', '-w', 'net.ipv4.ip_forward=1']
+        ip_wrapper.netns.execute(cmd)
+        if self.use_ipv6:
+            cmd = ['sysctl', '-w', 'net.ipv6.conf.all.forwarding=1']
+            ip_wrapper.netns.execute(cmd)
+
+    def delete(self):
+        if self.agent_conf.router_delete_namespaces:
+            try:
+                self.ip_wrapper_root.netns.delete(self.name)
+            except RuntimeError:
+                msg = _LE('Failed trying to delete namespace: %s')
+                LOG.exception(msg, self.name)
+
+
+class RouterNamespace(Namespace):
+
+    def __init__(self, router_id, agent_conf, driver, use_ipv6):
+        self.router_id = router_id
+        name = self._get_ns_name(router_id)
+        super(RouterNamespace, self).__init__(
+            name, agent_conf, driver, use_ipv6)
+
+    @staticmethod
+    def _get_ns_name(router_id):
+        return (NS_PREFIX + router_id)
+
+    def delete(self):
+        ns_ip = ip_lib.IPWrapper(namespace=self.name)
+        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=self.name,
+                                   prefix=INTERNAL_DEV_PREFIX)
+            elif d.name.startswith(ROUTER_2_FIP_DEV_PREFIX):
+                ns_ip.del_veth(d.name)
+            elif d.name.startswith(EXTERNAL_DEV_PREFIX):
+                self.driver.unplug(
+                    d.name,
+                    bridge=self.agent_conf.external_network_bridge,
+                    namespace=self.name,
+                    prefix=EXTERNAL_DEV_PREFIX)
+
+        super(RouterNamespace, self).delete()
index ba530a2d5caf1d3f1196a774efc63aca44fcb403..51371686c24434c102633ddceed69ef142881792 100644 (file)
@@ -14,6 +14,7 @@
 
 import netaddr
 
+from neutron.agent.l3 import namespaces
 from neutron.agent.linux import ip_lib
 from neutron.agent.linux import iptables_manager
 from neutron.common import constants as l3_constants
@@ -32,8 +33,7 @@ class RouterInfo(object):
                  router,
                  agent_conf,
                  interface_driver,
-                 use_ipv6=False,
-                 ns_name=None):
+                 use_ipv6=False):
         self.router_id = router_id
         self.ex_gw_port = None
         self._snat_enabled = None
@@ -42,7 +42,14 @@ class RouterInfo(object):
         self.floating_ips = set()
         # Invoke the setter for establishing initial SNAT action
         self.router = router
-        self.ns_name = ns_name
+        self.use_ipv6 = use_ipv6
+        self.ns_name = None
+        self.router_namespace = None
+        if agent_conf.use_namespaces:
+            ns = namespaces.RouterNamespace(
+                router_id, agent_conf, interface_driver, use_ipv6)
+            self.router_namespace = ns
+            self.ns_name = ns.name
         self.iptables_manager = iptables_manager.IptablesManager(
             use_ipv6=use_ipv6,
             namespace=self.ns_name)
@@ -225,3 +232,12 @@ class RouterInfo(object):
         for fip in self.router.get(l3_constants.FLOATINGIP_KEY, []):
             fip_statuses[fip['id']] = l3_constants.FLOATINGIP_STATUS_ERROR
         return fip_statuses
+
+    def create(self):
+        if self.router_namespace:
+            self.router_namespace.create()
+
+    def delete(self):
+        self.radvd.disable()
+        if self.router_namespace:
+            self.router_namespace.delete()
index 35829ce878e5dee9e11cf70d7fdda2473fb105a2..ebf536c7458726ad018721b5a673131698940146 100644 (file)
@@ -373,6 +373,10 @@ class NetworkIdOrRouterIdRequiredError(NeutronException):
     message = _('network_id and router_id are None. One must be provided.')
 
 
+class AbortSyncRouters(NeutronException):
+    message = _("Aborting periodic_sync_routers_task due to an error")
+
+
 # Shared *aas exceptions, pending them being refactored out of Neutron
 # proper.
 
diff --git a/neutron/tests/common/agents/l3_agent.py b/neutron/tests/common/agents/l3_agent.py
deleted file mode 100644 (file)
index 529ecef..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2014 Red Hat, Inc.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-
-from neutron.agent.l3 import agent
-
-
-class TestL3NATAgent(agent.L3NATAgentWithStateReport):
-    NESTED_NAMESPACE_SEPARATOR = '@'
-
-    def get_ns_name(self, router_id):
-        ns_name = super(TestL3NATAgent, self).get_ns_name(router_id)
-        return "%s%s%s" % (ns_name, self.NESTED_NAMESPACE_SEPARATOR, self.host)
-
-    def get_router_id(self, ns_name):
-        # 'ns_name' should be in the format of: 'qrouter-<id>@<host>'.
-        return super(TestL3NATAgent, self).get_router_id(
-            ns_name.split(self.NESTED_NAMESPACE_SEPARATOR)[0])
index 0c1b0ce899ef8f559e348f05aa016273ec7ce6ce..c98f3b246ec8d597bc930caa88df4ae2af7ea692 100755 (executable)
@@ -26,6 +26,8 @@ import webob.exc
 
 from neutron.agent.common import config as agent_config
 from neutron.agent.l3 import agent as neutron_l3_agent
+from neutron.agent.l3 import dvr_snat_ns
+from neutron.agent.l3 import namespaces
 from neutron.agent import l3_agent as l3_agent_main
 from neutron.agent.linux import dhcp
 from neutron.agent.linux import external_process
@@ -39,7 +41,6 @@ from neutron.common import utils as common_utils
 from neutron.openstack.common import log as logging
 from neutron.openstack.common import uuidutils
 from neutron.services import advanced_service as adv_svc
-from neutron.tests.common.agents import l3_agent as l3_test_agent
 from neutron.tests.functional.agent.linux import base
 from neutron.tests.functional.agent.linux import helpers
 from neutron.tests.unit import test_l3_agent
@@ -93,7 +94,7 @@ class L3AgentTestFramework(base.BaseOVSLinuxTestCase):
         conf.set_override('external_pids',
                           get_temp_file_path('external/pids'))
         conf.set_override('host', host)
-        agent = l3_test_agent.TestL3NATAgent(host, conf)
+        agent = neutron_l3_agent.L3NATAgentWithStateReport(host, conf)
         mock.patch.object(ip_lib, 'send_gratuitous_arp').start()
 
         return agent
@@ -475,13 +476,16 @@ class L3AgentTestCase(L3AgentTestFramework):
         router_info = self.generate_router_info(enable_ha=True)
         router1 = self._create_router(self.agent, router_info)
         self._add_fip(router1, '192.168.111.12')
-        restarted_agent = l3_test_agent.TestL3NATAgent(self.agent.host,
-                                                       self.agent.conf)
+        restarted_agent = neutron_l3_agent.L3NATAgentWithStateReport(
+            self.agent.host, self.agent.conf)
         self._create_router(restarted_agent, router1.router)
         utils.wait_until_true(lambda: self._floating_ips_configured(router1))
 
 
 class L3HATestFramework(L3AgentTestFramework):
+
+    NESTED_NAMESPACE_SEPARATOR = '@'
+
     def setUp(self):
         super(L3HATestFramework, self).setUp()
         self.failover_agent = self._configure_agent('agent2')
@@ -497,6 +501,11 @@ class L3HATestFramework(L3AgentTestFramework):
 
     def test_ha_router_failover(self):
         router_info = self.generate_router_info(enable_ha=True)
+        ns_name = "%s%s%s" % (
+                namespaces.RouterNamespace._get_ns_name(router_info['id']),
+                self.NESTED_NAMESPACE_SEPARATOR, self.agent.host)
+        mock.patch.object(namespaces.RouterNamespace, '_get_ns_name',
+                return_value=ns_name).start()
         router1 = self.manage_router(self.agent, router_info)
 
         router_info_2 = copy.deepcopy(router_info)
@@ -504,6 +513,11 @@ class L3HATestFramework(L3AgentTestFramework):
             test_l3_agent.get_ha_interface(ip='169.254.192.2',
                                            mac='22:22:22:22:22:22'))
 
+        ns_name = "%s%s%s" % (
+                namespaces.RouterNamespace._get_ns_name(router_info_2['id']),
+                self.NESTED_NAMESPACE_SEPARATOR, self.failover_agent.host)
+        mock.patch.object(namespaces.RouterNamespace, '_get_ns_name',
+                return_value=ns_name).start()
         router2 = self.manage_router(self.failover_agent, router_info_2)
 
         utils.wait_until_true(lambda: router1.ha_state == 'master')
@@ -719,7 +733,8 @@ class TestDvrRouter(L3AgentTestFramework):
 
     def _assert_dvr_external_device(self, router):
         external_port = router.get_ex_gw_port()
-        snat_ns_name = self.agent.get_snat_ns_name(router.router_id)
+        snat_ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(
+            router.router_id)
 
         # if the agent is in dvr_snat mode, then we have to check
         # that the correct ports and ip addresses exist in the
@@ -755,7 +770,8 @@ class TestDvrRouter(L3AgentTestFramework):
             self._assert_snat_namespace_does_not_exist(router)
 
     def _assert_dvr_snat_gateway(self, router):
-        namespace = self.agent.get_snat_ns_name(router.router_id)
+        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_port['id'])
@@ -767,7 +783,8 @@ class TestDvrRouter(L3AgentTestFramework):
         self.assertEqual(expected_gateway, existing_gateway)
 
     def _assert_snat_namespace_does_not_exist(self, router):
-        namespace = self.agent.get_snat_ns_name(router.router_id)
+        namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name(
+            router.router_id)
         self.assertFalse(self._namespace_exists(namespace))
 
     def _assert_dvr_floating_ips(self, router):
index 9c05b6535d64334fcf48ee98c9fc5b2aed71fe78..835e8bc5ae90baecd0ef8a8a4fcfb181e2fc0a5f 100644 (file)
@@ -35,8 +35,9 @@ class TestDvrRouterOperations(base.BaseTestCase):
 
     def _create_router(self, router, **kwargs):
         agent_conf = mock.Mock()
+        self.router_id = _uuid()
         return dvr_router.DvrRouter(mock.sentinel.myhost,
-                                    mock.sentinel.router_id,
+                                    self.router_id,
                                     router,
                                     agent_conf,
                                     mock.sentinel.interface_driver,
index 180093697d22d9bc5c8040ef146a9e71f38553d7..486c63dbf9beee941cbfabf8435608c8868b3484 100644 (file)
 import mock
 
 from neutron.agent.l3 import ha_router
+from neutron.openstack.common import uuidutils
 from neutron.tests import base
 
+_uuid = uuidutils.generate_uuid
+
 
 class TestBasicRouterOperations(base.BaseTestCase):
     def setUp(self):
@@ -25,11 +28,14 @@ class TestBasicRouterOperations(base.BaseTestCase):
     def _create_router(self, router=None, **kwargs):
         if not router:
             router = mock.MagicMock()
-        return ha_router.HaRouter(mock.sentinel.router_id,
+        self.agent_conf = mock.Mock()
+        # NOTE The use_namespaces config will soon be deprecated
+        self.agent_conf.use_namespaces = True
+        self.router_id = _uuid()
+        return ha_router.HaRouter(self.router_id,
                                   router,
-                                  mock.sentinel.agent_conf,
+                                  self.agent_conf,
                                   mock.sentinel.driver,
-                                  ns_name=mock.sentinel.namespace,
                                   **kwargs)
 
     def test_get_router_cidrs_returns_ha_cidrs(self):
index 66e8ad8bf503eb6600315407444e4b4fa7092bce..c275bec1e46faa6763799c8a21efa454125c9838 100644 (file)
@@ -28,9 +28,13 @@ class BasicRouterTestCaseFramework(base.BaseTestCase):
     def _create_router(self, router=None, **kwargs):
         if not router:
             router = mock.MagicMock()
-        return router_info.RouterInfo(mock.sentinel.router_id,
+        self.agent_conf = mock.Mock()
+        # NOTE The use_namespaces config will soon be deprecated
+        self.agent_conf.use_namespaces = True
+        self.router_id = _uuid()
+        return router_info.RouterInfo(self.router_id,
                                       router,
-                                      mock.sentinel.agent_conf,
+                                      self.agent_conf,
                                       mock.sentinel.interface_driver,
                                       **kwargs)
 
index 085765659ca747c42b63900eb9dad26f77764c45..af037b539c6b33585ff14776f48674b5b0fe0a72 100644 (file)
@@ -17,8 +17,11 @@ import mock
 from neutron.agent.l3 import legacy_router
 from neutron.agent.linux import ip_lib
 from neutron.common import constants as l3_constants
+from neutron.openstack.common import uuidutils
 from neutron.tests import base
 
+_uuid = uuidutils.generate_uuid
+
 
 class BasicRouterTestCaseFramework(base.BaseTestCase):
     def _create_router(self, router=None, **kwargs):
@@ -26,11 +29,11 @@ class BasicRouterTestCaseFramework(base.BaseTestCase):
             router = mock.MagicMock()
         self.agent_conf = mock.Mock()
         self.driver = mock.Mock()
-        return legacy_router.LegacyRouter(mock.sentinel.router_id,
+        self.router_id = _uuid()
+        return legacy_router.LegacyRouter(self.router_id,
                                           router,
                                           self.agent_conf,
                                           self.driver,
-                                          ns_name=mock.sentinel.namespace,
                                           **kwargs)
 
 
@@ -46,7 +49,7 @@ class TestBasicRouterOperations(BasicRouterTestCaseFramework):
         device.addr.delete.assert_called_once_with(4, cidr)
         self.driver.delete_conntrack_state.assert_called_once_with(
             ip=cidr,
-            namespace=mock.sentinel.namespace)
+            namespace=ri.ns_name)
 
 
 @mock.patch.object(ip_lib, 'send_gratuitous_arp')
@@ -55,13 +58,12 @@ class TestAddFloatingIpWithMockGarp(BasicRouterTestCaseFramework):
         ri = self._create_router()
         ri._add_fip_addr_to_device = mock.Mock(return_value=True)
         self.agent_conf.send_arp_for_ha = mock.sentinel.arp_count
-        ri.ns_name = mock.sentinel.ns_name
         ip = '15.1.2.3'
         result = ri.add_floating_ip({'floating_ip_address': ip},
                                     mock.sentinel.interface_name,
                                     mock.sentinel.device)
         ip_lib.send_gratuitous_arp.assert_called_once_with(
-            mock.sentinel.ns_name,
+            ri.ns_name,
             mock.sentinel.interface_name,
             ip,
             mock.sentinel.arp_count)
index 8be507754024f76038169ea8fc8a5b04cb8e17cd..acb764d373cace7133a56d96819f57c24f5c35d6 100644 (file)
@@ -26,6 +26,7 @@ class TestRouterInfo(base.BaseTestCase):
         super(TestRouterInfo, self).setUp()
 
         conf = agent_config.setup_conf()
+        conf.use_namespaces = True
 
         self.ip_cls_p = mock.patch('neutron.agent.linux.ip_lib.IPWrapper')
         ip_cls = self.ip_cls_p.start()
@@ -40,8 +41,7 @@ class TestRouterInfo(base.BaseTestCase):
             any_order=True)
 
     def test_routing_table_update(self):
-        ri = router_info.RouterInfo(_uuid(), {}, ns_name=_uuid(),
-                                    **self.ri_kwargs)
+        ri = router_info.RouterInfo(_uuid(), {}, **self.ri_kwargs)
         ri.router = {}
 
         fake_route1 = {'destination': '135.207.0.0/16',
@@ -70,8 +70,7 @@ class TestRouterInfo(base.BaseTestCase):
         self._check_agent_method_called(expected)
 
     def test_routes_updated(self):
-        ri = router_info.RouterInfo(_uuid(), {}, ns_name=_uuid(),
-                                    **self.ri_kwargs)
+        ri = router_info.RouterInfo(_uuid(), {}, **self.ri_kwargs)
         ri.router = {}
 
         fake_old_routes = []
index 57da7ec32f22f93aaf52fad0d0af7659d861300b..51b951f4298305952b0f3a5d04a697ae4dd946f6 100644 (file)
@@ -97,7 +97,9 @@ class TestDvrFipNs(base.BaseTestCase):
         dev2.name = 'fg-aaaa'
         ip_wrapper.get_devices.return_value = [dev1, dev2]
 
-        self.fip_ns.destroy()
+        self.conf.router_delete_namespaces = False
+
+        self.fip_ns.delete()
 
         ext_net_bridge = self.conf.external_network_bridge
         ns_name = self.fip_ns.get_name()
index d26744a3b7728b44b09ba7357159f084c856e2c8..c6dea6f6218850f4cee153928483e741e449699b 100644 (file)
@@ -25,11 +25,12 @@ from testtools import matchers
 from neutron.agent.common import config as agent_config
 from neutron.agent.l3 import agent as l3_agent
 from neutron.agent.l3 import config as l3_config
-from neutron.agent.l3 import dvr
 from neutron.agent.l3 import dvr_router
+from neutron.agent.l3 import dvr_snat_ns
 from neutron.agent.l3 import ha
 from neutron.agent.l3 import legacy_router
 from neutron.agent.l3 import link_local_allocator as lla
+from neutron.agent.l3 import namespaces
 from neutron.agent.l3 import router_info as l3router
 from neutron.agent.linux import external_process
 from neutron.agent.linux import interface
@@ -282,8 +283,9 @@ class BasicRouterOperationsFramework(base.BaseTestCase):
         network_id = _uuid()
         router = prepare_router_data(num_internal_ports=2)
         router_id = router['id']
-        ri = l3router.RouterInfo(router_id, router, **self.ri_kwargs)
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        ri = l3router.RouterInfo(router_id, router,
+                                 **self.ri_kwargs)
         cidr = '99.0.1.9/24'
         mac = 'ca:fe:de:ad:be:ef'
         port = {'network_id': network_id,
@@ -305,12 +307,11 @@ class BasicRouterOperationsFramework(base.BaseTestCase):
 class TestBasicRouterOperations(BasicRouterOperationsFramework):
     def test_periodic_sync_routers_task_raise_exception(self):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        self.plugin_api.get_routers.side_effect = ValueError()
-        with mock.patch.object(agent, '_cleanup_namespaces') as f:
-            self.assertRaises(ValueError, agent.periodic_sync_routers_task,
-                              agent.context)
-            self.assertTrue(agent.fullsync)
-        self.assertFalse(f.called)
+        self.plugin_api.get_routers.side_effect = ValueError
+        self.assertRaises(ValueError,
+                          agent.periodic_sync_routers_task,
+                          agent.context)
+        self.assertTrue(agent.fullsync)
 
     def test_l3_initial_full_sync_done(self):
         with mock.patch.object(l3_agent.L3NATAgent,
@@ -323,14 +324,12 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
     def test_periodic_sync_routers_task_call_clean_stale_namespaces(self):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         self.plugin_api.get_routers.return_value = []
-        with mock.patch.object(agent, '_cleanup_namespaces') as f:
-            agent.periodic_sync_routers_task(agent.context)
-        self.assertTrue(f.called)
+        agent.periodic_sync_routers_task(agent.context)
+        self.assertFalse(agent.namespaces_manager._clean_stale)
 
     def test_router_info_create(self):
         id = _uuid()
-        ns = "ns-" + id
-        ri = l3router.RouterInfo(id, {}, ns_name=ns, **self.ri_kwargs)
+        ri = l3router.RouterInfo(id, {}, **self.ri_kwargs)
 
         self.assertTrue(ri.ns_name.endswith(id))
 
@@ -347,8 +346,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             'enable_snat': True,
             'routes': [],
             'gw_port': ex_gw_port}
-        ns = "ns-" + id
-        ri = l3router.RouterInfo(id, router, ns_name=ns, **self.ri_kwargs)
+        ri = l3router.RouterInfo(id, router, **self.ri_kwargs)
         self.assertTrue(ri.ns_name.endswith(id))
         self.assertEqual(ri.router, router)
 
@@ -400,13 +398,13 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             self.assertEqual(agent._set_subnet_info.call_count, 1)
             self.assertEqual(agent._internal_network_added.call_count, 2)
             agent._internal_network_added.assert_called_with(
-                agent.get_snat_ns_name(ri.router['id']),
+                dvr_snat_ns.SnatNamespace.get_snat_ns_name(ri.router['id']),
                 sn_port['network_id'],
                 sn_port['id'],
                 sn_port['ip_cidr'],
                 sn_port['mac_address'],
                 agent.get_snat_int_device_name(sn_port['id']),
-                dvr.SNAT_INT_DEV_PREFIX)
+                dvr_snat_ns.SNAT_INT_DEV_PREFIX)
 
     def test_agent_add_internal_network(self):
         self._test_internal_network_action('add')
@@ -419,9 +417,6 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
 
     def _test_external_gateway_action(self, action, router):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        ri = l3router.RouterInfo(router['id'], router,
-                                 ns_name=agent.get_ns_name(router['id']),
-                                 **self.ri_kwargs)
         ex_net_id = _uuid()
         # Special setup for dvr routers
         if router.get('distributed'):
@@ -429,7 +424,16 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             agent.host = HOSTNAME
             agent._create_dvr_gateway = mock.Mock()
             agent.get_snat_interfaces = mock.Mock(return_value=self.snat_ports)
+            ri = dvr_router.DvrRouter(HOSTNAME,
+                                      router['id'],
+                                      router,
+                                      **self.ri_kwargs)
+            ri.create_snat_namespace()
             ri.fip_ns = agent.get_fip_ns(ex_net_id)
+        else:
+            ri = l3router.RouterInfo(
+                router['id'], router,
+                **self.ri_kwargs)
 
         ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
                                      'subnet_id': _uuid()}],
@@ -496,9 +500,7 @@ 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,
-                                 ns_name=agent.get_ns_name(router['id']),
-                                 **self.ri_kwargs)
+        ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
         interface_name, ex_gw_port = self._prepare_ext_gw_test(agent)
 
         fake_fip = {'floatingips': [{'id': _uuid(),
@@ -523,8 +525,12 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
     def _test_ext_gw_updated_dvr_agent_mode(self, host,
                                             agent_mode, expected_call_count):
         router = prepare_router_data(num_internal_ports=2)
-        ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+        ri = dvr_router.DvrRouter(HOSTNAME,
+                                  router['id'],
+                                  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()
 
@@ -722,7 +728,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         ri = l3router.RouterInfo(
             'foo_router_id',
-            {'distributed': True, 'gw_port_host': HOSTNAME}, **self.ri_kwargs)
+            {'distributed': True, 'gw_port_host': HOSTNAME},
+            **self.ri_kwargs)
         with mock.patch.object(l3_agent.ip_lib, 'IPDevice') as f:
             agent._update_arp_entry(ri, mock.ANY, mock.ANY,
                                     'foo_subnet_id', 'add')
@@ -748,11 +755,13 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
 
     def test_process_cent_router(self):
         router = prepare_router_data()
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
-        self._test_process_router(ri)
+        self._test_process_router(ri, agent)
 
     def test_process_dist_router(self):
         router = prepare_router_data()
+        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         ri = dvr_router.DvrRouter(HOSTNAME,
                                   router['id'],
                                   router,
@@ -763,11 +772,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             'fixed_ips': [{'subnet_id': subnet_id,
                            'ip_address': '1.2.3.4'}]}]
         ri.router['gw_port_host'] = None
-        self._test_process_router(ri)
+        self._test_process_router(ri, agent)
 
-    def _test_process_router(self, ri):
+    def _test_process_router(self, ri, agent):
         router = ri.router
-        agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         agent.host = HOSTNAME
         fake_fip_id = 'fake_fip_id'
         agent.create_dvr_fip_interfaces = mock.Mock()
@@ -944,9 +952,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
 
         router = prepare_router_data(enable_snat=True)
         router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips']
-        ri = legacy_router.LegacyRouter(router['id'], router, **self.ri_kwargs)
-        ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
         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')
         self._test_process_floating_ip_addresses_add(ri, agent)
 
@@ -1443,7 +1451,11 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
 
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
 
-        agent._destroy_namespace(namespace)
+        ns = namespaces.RouterNamespace(
+            'bar', self.conf, agent.driver, agent.use_ipv6)
+        ns.create()
+
+        ns.delete()
         self.mock_driver.unplug.assert_called_once_with('qr-aaaa',
                                                         prefix='qr-',
                                                         namespace='qrouter'
@@ -1452,14 +1464,20 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
 
     def test_destroy_router_namespace_skips_ns_removal(self):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        agent._destroy_router_namespace("fakens")
+        ns = namespaces.Namespace(
+            'qrouter-bar', self.conf, agent.driver, agent.use_ipv6)
+        ns.create()
+        ns.delete()
         self.assertEqual(self.mock_ip.netns.delete.call_count, 0)
 
     def test_destroy_router_namespace_removes_ns(self):
         self.conf.set_override('router_delete_namespaces', True)
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
-        agent._destroy_router_namespace("fakens")
-        self.mock_ip.netns.delete.assert_called_once_with("fakens")
+        ns = namespaces.Namespace(
+            'qrouter-bar', self.conf, agent.driver, agent.use_ipv6)
+        ns.create()
+        ns.delete()
+        self.mock_ip.netns.delete.assert_called_once_with("qrouter-bar")
 
     def _configure_metadata_proxy(self, enableflag=True):
         if not enableflag:
@@ -1509,7 +1527,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         self.conf.set_override('router_id', '1234')
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         self.assertEqual('1234', agent.conf.router_id)
-        self.assertFalse(agent._clean_stale_namespaces)
+        self.assertFalse(agent.namespaces_manager._clean_stale)
 
     def test_process_routers_update_rpc_timeout_on_get_routers(self):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
@@ -1644,15 +1662,19 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             msg = _LE("Error importing interface driver '%s'")
             log.error.assert_called_once_with(msg, 'wrong_driver')
 
+    @mock.patch.object(namespaces.RouterNamespace, 'delete')
+    @mock.patch.object(dvr_snat_ns.SnatNamespace, 'delete')
     def _cleanup_namespace_test(self,
                                 stale_namespace_list,
                                 router_list,
-                                other_namespaces):
+                                other_namespaces,
+                                mock_snat_ns,
+                                mock_router_ns):
         self.conf.set_override('router_delete_namespaces', True)
 
-        good_namespace_list = [l3_agent.NS_PREFIX + r['id']
+        good_namespace_list = [namespaces.NS_PREFIX + r['id']
                                for r in router_list]
-        good_namespace_list += [dvr.SNAT_NS_PREFIX + r['id']
+        good_namespace_list += [dvr_snat_ns.SNAT_NS_PREFIX + r['id']
                                 for r in router_list]
         self.mock_ip.get_namespaces.return_value = (stale_namespace_list +
                                                     good_namespace_list +
@@ -1660,34 +1682,27 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
 
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
 
-        self.assertTrue(agent._clean_stale_namespaces)
+        self.assertTrue(agent.namespaces_manager._clean_stale)
 
         pm = self.external_process.return_value
         pm.reset_mock()
 
-        agent._destroy_router_namespace = mock.MagicMock()
-        agent._destroy_snat_namespace = mock.MagicMock()
-        ns_list = agent._list_namespaces()
-        agent._cleanup_namespaces(ns_list, [r['id'] for r in router_list])
-
-        # Expect process manager to disable metadata proxy per qrouter ns
+        with agent.namespaces_manager as ns_manager:
+            for r in router_list:
+                ns_manager.keep_router(r['id'])
         qrouters = [n for n in stale_namespace_list
-                    if n.startswith(l3_agent.NS_PREFIX)]
+                    if n.startswith(namespaces.NS_PREFIX)]
+        self.assertEqual(mock_router_ns.call_count, len(qrouters))
+        self.assertEqual(mock_snat_ns.call_count,
+            len(stale_namespace_list) - len(qrouters))
 
-        self.assertEqual(agent._destroy_router_namespace.call_count,
-                         len(qrouters))
-        self.assertEqual(agent._destroy_snat_namespace.call_count,
-                         len(stale_namespace_list) - len(qrouters))
-        expected_args = [mock.call(ns) for ns in qrouters]
-        agent._destroy_router_namespace.assert_has_calls(expected_args,
-                                                         any_order=True)
-        self.assertFalse(agent._clean_stale_namespaces)
+        self.assertFalse(agent.namespaces_manager._clean_stale)
 
     def test_cleanup_namespace(self):
         self.conf.set_override('router_id', None)
-        stale_namespaces = [l3_agent.NS_PREFIX + 'foo',
-                            l3_agent.NS_PREFIX + 'bar',
-                            dvr.SNAT_NS_PREFIX + 'foo']
+        stale_namespaces = [namespaces.NS_PREFIX + 'foo',
+                            namespaces.NS_PREFIX + 'bar',
+                            dvr_snat_ns.SNAT_NS_PREFIX + 'foo']
         other_namespaces = ['unknown']
 
         self._cleanup_namespace_test(stale_namespaces,
@@ -1696,9 +1711,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
 
     def test_cleanup_namespace_with_registered_router_ids(self):
         self.conf.set_override('router_id', None)
-        stale_namespaces = [l3_agent.NS_PREFIX + 'cccc',
-                            l3_agent.NS_PREFIX + 'eeeee',
-                            dvr.SNAT_NS_PREFIX + 'fffff']
+        stale_namespaces = [namespaces.NS_PREFIX + 'cccc',
+                            namespaces.NS_PREFIX + 'eeeee',
+                            dvr_snat_ns.SNAT_NS_PREFIX + 'fffff']
         router_list = [{'id': 'foo', 'distributed': False},
                        {'id': 'aaaa', 'distributed': False}]
         other_namespaces = ['qdhcp-aabbcc', 'unknown']
@@ -1709,9 +1724,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
 
     def test_cleanup_namespace_with_conf_router_id(self):
         self.conf.set_override('router_id', 'bbbbb')
-        stale_namespaces = [l3_agent.NS_PREFIX + 'cccc',
-                            l3_agent.NS_PREFIX + 'eeeee',
-                            l3_agent.NS_PREFIX + self.conf.router_id]
+        stale_namespaces = [namespaces.NS_PREFIX + 'cccc',
+                            namespaces.NS_PREFIX + 'eeeee',
+                            namespaces.NS_PREFIX + self.conf.router_id]
         router_list = [{'id': 'foo', 'distributed': False},
                        {'id': 'aaaa', 'distributed': False}]
         other_namespaces = ['qdhcp-aabbcc', 'unknown']
@@ -1723,7 +1738,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
     def test_create_dvr_gateway(self):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         router = prepare_router_data()
-        ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
+        ri = dvr_router.DvrRouter(HOSTNAME,
+                                  router['id'],
+                                  router,
+                                  **self.ri_kwargs)
 
         port_id = _uuid()
         dvr_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
@@ -1865,10 +1883,11 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         mock.patch(ensure_dir).start()
         execute = mock.patch(utils_execute).start()
 
-        radvd = ra.DaemonMonitor(router['id'],
-                                 agent.get_ns_name(router['id']),
-                                 agent.process_monitor,
-                                 FakeDev)
+        radvd = ra.DaemonMonitor(
+            router['id'],
+            namespaces.RouterNamespace._get_ns_name(router['id']),
+            agent.process_monitor,
+            FakeDev)
         radvd.enable(router['_interfaces'])
 
         cmd = execute.call_args[0][0]