From: Miguel Lavalle Date: Fri, 16 Jan 2015 04:16:01 +0000 (-0600) Subject: Refactor management of namespaces in the L3 Agent X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=01a7ba19cf6661b1aef7d08fb748bb2470caf28f;p=openstack-build%2Fneutron-build.git Refactor management of namespaces in the L3 Agent 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 --- diff --git a/neutron/agent/l3/agent.py b/neutron/agent/l3/agent.py index 2efbf4b94..5abcf88c2 100644 --- a/neutron/agent/l3/agent.py +++ b/neutron/agent/l3/agent.py @@ -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")) diff --git a/neutron/agent/l3/dvr.py b/neutron/agent/l3/dvr.py index fab6f0575..20066e0b9 100644 --- a/neutron/agent/l3/dvr.py +++ b/neutron/agent/l3/dvr.py @@ -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) diff --git a/neutron/agent/l3/dvr_fip_ns.py b/neutron/agent/l3/dvr_fip_ns.py index a8bd68014..5369fc8b3 100644 --- a/neutron/agent/l3/dvr_fip_ns.py +++ b/neutron/agent/l3/dvr_fip_ns.py @@ -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. diff --git a/neutron/agent/l3/dvr_router.py b/neutron/agent/l3/dvr_router.py index b85326495..dfb30e704 100644 --- a/neutron/agent/l3/dvr_router.py +++ b/neutron/agent/l3/dvr_router.py @@ -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 index 000000000..2ab940bc1 --- /dev/null +++ b/neutron/agent/l3/dvr_snat_ns.py @@ -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 index 000000000..21f72cb0a --- /dev/null +++ b/neutron/agent/l3/namespace_manager.py @@ -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 index 000000000..27111ac06 --- /dev/null +++ b/neutron/agent/l3/namespaces.py @@ -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() diff --git a/neutron/agent/l3/router_info.py b/neutron/agent/l3/router_info.py index ba530a2d5..51371686c 100644 --- a/neutron/agent/l3/router_info.py +++ b/neutron/agent/l3/router_info.py @@ -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() diff --git a/neutron/common/exceptions.py b/neutron/common/exceptions.py index 35829ce87..ebf536c74 100644 --- a/neutron/common/exceptions.py +++ b/neutron/common/exceptions.py @@ -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 index 529ecefe0..000000000 --- a/neutron/tests/common/agents/l3_agent.py +++ /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-@'. - return super(TestL3NATAgent, self).get_router_id( - ns_name.split(self.NESTED_NAMESPACE_SEPARATOR)[0]) diff --git a/neutron/tests/functional/agent/test_l3_agent.py b/neutron/tests/functional/agent/test_l3_agent.py index 0c1b0ce89..c98f3b246 100755 --- a/neutron/tests/functional/agent/test_l3_agent.py +++ b/neutron/tests/functional/agent/test_l3_agent.py @@ -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): diff --git a/neutron/tests/unit/agent/l3/test_dvr_router.py b/neutron/tests/unit/agent/l3/test_dvr_router.py index 9c05b6535..835e8bc5a 100644 --- a/neutron/tests/unit/agent/l3/test_dvr_router.py +++ b/neutron/tests/unit/agent/l3/test_dvr_router.py @@ -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, diff --git a/neutron/tests/unit/agent/l3/test_ha_router.py b/neutron/tests/unit/agent/l3/test_ha_router.py index 180093697..486c63dbf 100644 --- a/neutron/tests/unit/agent/l3/test_ha_router.py +++ b/neutron/tests/unit/agent/l3/test_ha_router.py @@ -15,8 +15,11 @@ 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): diff --git a/neutron/tests/unit/agent/l3/test_l3_router.py b/neutron/tests/unit/agent/l3/test_l3_router.py index 66e8ad8bf..c275bec1e 100644 --- a/neutron/tests/unit/agent/l3/test_l3_router.py +++ b/neutron/tests/unit/agent/l3/test_l3_router.py @@ -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) diff --git a/neutron/tests/unit/agent/l3/test_legacy_router.py b/neutron/tests/unit/agent/l3/test_legacy_router.py index 085765659..af037b539 100644 --- a/neutron/tests/unit/agent/l3/test_legacy_router.py +++ b/neutron/tests/unit/agent/l3/test_legacy_router.py @@ -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) diff --git a/neutron/tests/unit/agent/l3/test_router_info.py b/neutron/tests/unit/agent/l3/test_router_info.py index 8be507754..acb764d37 100644 --- a/neutron/tests/unit/agent/l3/test_router_info.py +++ b/neutron/tests/unit/agent/l3/test_router_info.py @@ -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 = [] diff --git a/neutron/tests/unit/agent/test_dvr_fip_ns.py b/neutron/tests/unit/agent/test_dvr_fip_ns.py index 57da7ec32..51b951f42 100644 --- a/neutron/tests/unit/agent/test_dvr_fip_ns.py +++ b/neutron/tests/unit/agent/test_dvr_fip_ns.py @@ -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() diff --git a/neutron/tests/unit/test_l3_agent.py b/neutron/tests/unit/test_l3_agent.py index d26744a3b..c6dea6f62 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -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]