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
This commit is contained in:
Miguel Lavalle 2015-01-15 22:16:01 -06:00 committed by Carl Baldwin
parent 1e93c0c5fb
commit 01a7ba19cf
18 changed files with 493 additions and 290 deletions

View 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"))

View 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)

View 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.

View 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

View File

@ -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()

View File

@ -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()

View File

@ -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()

View 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()

View 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.

View File

@ -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])

View File

@ -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):

View 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,

View File

@ -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):

View 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)

View 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)

View 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 = []

View 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()

View 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]