Encapsulate DVR Fip namespace

The floating ip namespace is an important concept that links a DVR
enabled L3 agent to DVR routers.  The namespace is shared by all of
the dvr routers on a compute host connected to the same network.  It
is managed by the dvr enabled agent.  Hence, it should be known to the
agent and made available to the DVR routers as well.  This calls for a
proper encapsulation of this functionality in a class which can be
accessed by both the agent and the routers.

In trying to tease floating ip functionality out of the L3 agent and
in to the new router classes, I found it very difficult to do because
the logic around the floating ip namespace has not yet been properly
encapsulated.

Change-Id: I0913cfae391164146bbb50ed42311506d6559e3f
Partially-Implements: bp/restructure-l3-agent
This commit is contained in:
Carl Baldwin 2015-01-07 19:25:41 +00:00
parent 9d1db4a01c
commit 59795ab607
7 changed files with 555 additions and 422 deletions

View File

@ -23,6 +23,7 @@ from oslo.utils import timeutils
from neutron.agent.common import config
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 event_observers
from neutron.agent.l3 import ha
@ -274,7 +275,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
def _destroy_namespace(self, ns):
if ns.startswith(NS_PREFIX):
self._destroy_router_namespace(ns)
elif ns.startswith(dvr.FIP_NS_PREFIX):
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)
@ -294,7 +295,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
# device is on default bridge
self.driver.unplug(d.name, namespace=ns,
prefix=INTERNAL_DEV_PREFIX)
elif d.name.startswith(dvr.ROUTER_2_FIP_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,
@ -588,7 +589,10 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
# TODO(mrsmith) - we shouldn't need to check here
if 'distributed' not in ri.router:
ri.router['distributed'] = False
self.scan_fip_ports(ri)
ex_gw_port = self._get_ex_gw_port(ri)
if ri.router.get('distributed') and ex_gw_port:
ri.fip_ns = self.get_fip_ns(ex_gw_port['network_id'])
ri.fip_ns.scan_fip_ports(ri)
self._process_internal_ports(ri)
self._process_external(ri)
# Process static routes for router
@ -598,7 +602,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
self._process_ha_router(ri)
# Update ex_gw_port and enable_snat on the router info cache
ri.ex_gw_port = self._get_ex_gw_port(ri)
ri.ex_gw_port = ex_gw_port
ri.snat_ports = ri.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, [])
ri.enable_snat = ri.router.get('enable_snat')
@ -660,25 +664,32 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
def create_dvr_fip_interfaces(self, ri, ex_gw_port):
floating_ips = self.get_floating_ips(ri)
if floating_ips:
is_first = self._fip_ns_subscribe(ri.router_id)
is_first = ri.fip_ns.subscribe(ri.router_id)
if is_first:
self._create_agent_gateway_port(ri, floating_ips[0]
['floating_network_id'])
agent_gateway_port = (
self.plugin_rpc.get_agent_gateway_port(
self.context, ex_gw_port['network_id']))
if 'subnet' not in agent_gateway_port:
LOG.error(_LE('Missing subnet/agent_gateway_port'))
else:
self._set_subnet_info(agent_gateway_port)
ri.fip_ns.create_gateway_port(agent_gateway_port)
if self.agent_gateway_port and floating_ips:
fip_net_id = floating_ips[0]['floating_network_id']
self.create_rtr_2_fip_link(ri, fip_net_id)
if ri.fip_ns.agent_gateway_port and floating_ips:
if ri.dist_fip_count == 0:
ri.fip_ns.create_rtr_2_fip_link(ri)
# kicks the FW Agent to add rules for the IR namespace if
# configured
self.process_router_add(ri)
def _get_external_device_interface_name(self, ri, ex_gw_port):
if ri.router['distributed']:
fip_int = self.get_fip_int_device_name(ri.router_id)
# TODO(mrsmith) refactor for multiple ext nets
fip_ns = self.get_fip_ns_name(str(self._fetch_external_net_id()))
fip_int = ri.fip_ns.get_int_device_name(ri.router_id)
if ip_lib.device_exists(fip_int,
root_helper=self.root_helper,
namespace=fip_ns):
return self.get_rtr_int_device_name(ri.router_id)
namespace=ri.fip_ns.get_name()):
return ri.fip_ns.get_rtr_ext_device_name(ri.router_id)
else:
return self.get_external_device_name(ex_gw_port['id'])

View File

@ -14,30 +14,18 @@
import binascii
import netaddr
import os
from neutron.agent.l3 import link_local_allocator as lla
from neutron.agent.l3 import dvr_fip_ns
from neutron.agent.linux import ip_lib
from neutron.agent.linux import iptables_manager
from neutron.common import constants as l3_constants
from neutron.common import utils as common_utils
from neutron.i18n import _LE
from neutron.openstack.common import log as logging
LOG = logging.getLogger(__name__)
SNAT_INT_DEV_PREFIX = 'sg-'
FIP_EXT_DEV_PREFIX = 'fg-'
FIP_NS_PREFIX = 'fip-'
SNAT_NS_PREFIX = 'snat-'
FIP_2_ROUTER_DEV_PREFIX = 'fpr-'
ROUTER_2_FIP_DEV_PREFIX = 'rfp-'
FIP_LL_SUBNET = '169.254.30.0/23'
# Rule priority range for FIPs
FIP_PR_START = 32768
FIP_PR_END = FIP_PR_START + 40000
# Route Table index for FIPs
FIP_RT_TBL = 16
# xor-folding mask used for IPv6 rule index
MASK_30 = 0x3fffffff
@ -45,24 +33,23 @@ MASK_30 = 0x3fffffff
class AgentMixin(object):
def __init__(self, host):
# dvr data
self.agent_gateway_port = None
self.fip_ns_subscribers = set()
self.local_subnets = lla.LinkLocalAllocator(
os.path.join(self.conf.state_path, 'fip-linklocal-networks'),
FIP_LL_SUBNET)
self.fip_priorities = set(range(FIP_PR_START,
FIP_PR_END))
self._fip_namespaces = {}
super(AgentMixin, self).__init__(host)
def _fip_ns_subscribe(self, router_id):
is_first = (len(self.fip_ns_subscribers) == 0)
self.fip_ns_subscribers.add(router_id)
return is_first
def get_fip_ns(self, ext_net_id):
# TODO(Carl) is this necessary? Code that this replaced was careful to
# convert these to string like this so I preserved that.
ext_net_id = str(ext_net_id)
def _fip_ns_unsubscribe(self, router_id):
self.fip_ns_subscribers.discard(router_id)
return len(self.fip_ns_subscribers) == 0
if ext_net_id not in self._fip_namespaces:
fip_ns = dvr_fip_ns.FipNamespace(ext_net_id,
self.conf,
self.driver,
self.root_helper,
self.use_ipv6)
self._fip_namespaces[ext_net_id] = fip_ns
return self._fip_namespaces[ext_net_id]
def _destroy_snat_namespace(self, ns):
ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=ns)
@ -79,24 +66,10 @@ class AgentMixin(object):
self._delete_namespace(ns_ip, ns)
def _destroy_fip_namespace(self, ns):
ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=ns)
for d in ns_ip.get_devices(exclude_loopback=True):
if d.name.startswith(FIP_2_ROUTER_DEV_PREFIX):
# internal link between IRs and FIP NS
ns_ip.del_veth(d.name)
elif d.name.startswith(FIP_EXT_DEV_PREFIX):
# single port from FIP NS to br-ext
# TODO(carl) Where does the port get deleted?
LOG.debug('DVR: unplug: %s', d.name)
self.driver.unplug(d.name,
bridge=self.conf.external_network_bridge,
namespace=ns,
prefix=FIP_EXT_DEV_PREFIX)
LOG.debug('DVR: destroy fip ns: %s', ns)
# TODO(mrsmith): add LOG warn if fip count != 0
if self.conf.router_delete_namespaces:
self._delete_namespace(ns_ip, ns)
self.agent_gateway_port = None
ex_net_id = ns[len(dvr_fip_ns.FIP_NS_PREFIX):]
fip_ns = self.get_fip_ns(ex_net_id)
del self._fip_namespaces[ex_net_id]
fip_ns.destroy()
def _set_subnet_arp_info(self, ri, port):
"""Set ARP info retrieved from Plugin for existing ports."""
@ -123,43 +96,10 @@ class AgentMixin(object):
if f['subnet_id'] == subnet_id:
return port
def scan_fip_ports(self, ri):
# don't scan if not dvr or count is not None
if not ri.router.get('distributed') or ri.dist_fip_count is not None:
return
# scan system for any existing fip ports
ri.dist_fip_count = 0
rtr_2_fip_interface = self.get_rtr_int_device_name(ri.router_id)
if ip_lib.device_exists(rtr_2_fip_interface,
root_helper=self.root_helper,
namespace=ri.ns_name):
device = ip_lib.IPDevice(rtr_2_fip_interface, self.root_helper,
namespace=ri.ns_name)
existing_cidrs = [addr['cidr'] for addr in device.addr.list()]
fip_cidrs = [c for c in existing_cidrs if
common_utils.is_cidr_host(c)]
ri.dist_fip_count = len(fip_cidrs)
def get_fip_ext_device_name(self, port_id):
return (FIP_EXT_DEV_PREFIX +
port_id)[:self.driver.DEV_NAME_LEN]
def get_rtr_int_device_name(self, router_id):
return (ROUTER_2_FIP_DEV_PREFIX +
router_id)[:self.driver.DEV_NAME_LEN]
def get_fip_int_device_name(self, router_id):
return (FIP_2_ROUTER_DEV_PREFIX +
router_id)[:self.driver.DEV_NAME_LEN]
def get_snat_int_device_name(self, port_id):
return (SNAT_INT_DEV_PREFIX +
port_id)[:self.driver.DEV_NAME_LEN]
def get_fip_ns_name(self, ext_net_id):
return (FIP_NS_PREFIX + ext_net_id)
def get_snat_ns_name(self, router_id):
return (SNAT_NS_PREFIX + router_id)
@ -205,35 +145,6 @@ class AgentMixin(object):
else:
LOG.error(_LE('DVR: no map match_port found!'))
def internal_ns_interface_added(self, ip_cidr,
interface_name, ns_name):
ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns_name)
ip_wrapper.netns.execute(['ip', 'addr', 'add',
ip_cidr, 'dev', interface_name])
def _handle_router_fip_nat_rules(self, ri, interface_name, action):
"""Configures NAT rules for Floating IPs for DVR.
Remove all the rules. This is safe because if
use_namespaces is set as False then the agent can
only configure one router, otherwise each router's
NAT rules will be in their own namespace.
"""
ri.iptables_manager.ipv4['nat'].empty_chain('POSTROUTING')
ri.iptables_manager.ipv4['nat'].empty_chain('snat')
# Add back the jump to float-snat
ri.iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat')
# And add them back if the action is add_rules
if action == 'add_rules' and interface_name:
rule = ('POSTROUTING', '! -i %(interface_name)s '
'! -o %(interface_name)s -m conntrack ! '
'--ctstate DNAT -j ACCEPT' %
{'interface_name': interface_name})
ri.iptables_manager.ipv4['nat'].add_rule(*rule)
ri.iptables_manager.apply()
def _create_dvr_gateway(self, ri, ex_gw_port, gw_interface_name,
snat_ports):
"""Create SNAT namespace."""
@ -257,116 +168,23 @@ class AgentMixin(object):
# kicks the FW Agent to add rules for the snat namespace
self.process_router_add(ri)
def agent_gateway_added(self, ns_name, ex_gw_port,
interface_name):
"""Add Floating IP gateway port to FIP namespace."""
if not ip_lib.device_exists(interface_name,
root_helper=self.root_helper,
namespace=ns_name):
self.driver.plug(ex_gw_port['network_id'],
ex_gw_port['id'], interface_name,
ex_gw_port['mac_address'],
bridge=self.conf.external_network_bridge,
namespace=ns_name,
prefix=FIP_EXT_DEV_PREFIX)
self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
namespace=ns_name)
ip_address = ex_gw_port['ip_cidr'].split('/')[0]
ip_lib.send_gratuitous_arp(ns_name,
interface_name,
ip_address,
self.conf.send_arp_for_ha,
self.root_helper)
gw_ip = ex_gw_port['subnet']['gateway_ip']
if gw_ip:
ipd = ip_lib.IPDevice(interface_name, self.root_helper,
namespace=ns_name)
ipd.route.add_gateway(gw_ip)
cmd = ['sysctl', '-w', 'net.ipv4.conf.%s.proxy_arp=1' % interface_name]
ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns_name)
ip_wrapper.netns.execute(cmd, check_exit_code=False)
def _create_agent_gateway_port(self, ri, network_id):
"""Create Floating IP gateway port.
Request port creation from Plugin then creates
Floating IP namespace and adds gateway port.
"""
self.agent_gateway_port = (
self.plugin_rpc.get_agent_gateway_port(
self.context, network_id))
if 'subnet' not in self.agent_gateway_port:
LOG.error(_LE('Missing subnet/agent_gateway_port'))
return
self._set_subnet_info(self.agent_gateway_port)
# add fip-namespace and agent_gateway_port
fip_ns_name = (
self.get_fip_ns_name(str(network_id)))
self._create_namespace(fip_ns_name)
ri.fip_iptables_manager = iptables_manager.IptablesManager(
root_helper=self.root_helper, namespace=fip_ns_name,
use_ipv6=self.use_ipv6)
# no connection tracking needed in fip namespace
ri.fip_iptables_manager.ipv4['raw'].add_rule('PREROUTING',
'-j CT --notrack')
ri.fip_iptables_manager.apply()
interface_name = (
self.get_fip_ext_device_name(self.agent_gateway_port['id']))
self.agent_gateway_added(fip_ns_name, self.agent_gateway_port,
interface_name)
def create_rtr_2_fip_link(self, ri, network_id):
"""Create interface between router and Floating IP namespace."""
rtr_2_fip_name = self.get_rtr_int_device_name(ri.router_id)
fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id)
fip_ns_name = self.get_fip_ns_name(str(network_id))
# add link local IP to interface
if ri.rtr_fip_subnet is None:
ri.rtr_fip_subnet = self.local_subnets.allocate(ri.router_id)
rtr_2_fip, fip_2_rtr = ri.rtr_fip_subnet.get_pair()
ip_wrapper = ip_lib.IPWrapper(self.root_helper,
namespace=ri.ns_name)
if not ip_lib.device_exists(rtr_2_fip_name, self.root_helper,
namespace=ri.ns_name):
int_dev = ip_wrapper.add_veth(rtr_2_fip_name,
fip_2_rtr_name, fip_ns_name)
self.internal_ns_interface_added(str(rtr_2_fip),
rtr_2_fip_name, ri.ns_name)
self.internal_ns_interface_added(str(fip_2_rtr),
fip_2_rtr_name, fip_ns_name)
int_dev[0].link.set_up()
int_dev[1].link.set_up()
# add default route for the link local interface
device = ip_lib.IPDevice(rtr_2_fip_name, self.root_helper,
namespace=ri.ns_name)
device.route.add_gateway(str(fip_2_rtr.ip), table=FIP_RT_TBL)
#setup the NAT rules and chains
self._handle_router_fip_nat_rules(ri, rtr_2_fip_name, 'add_rules')
# kicks the FW Agent to add rules for the IR namespace if configured
self.process_router_add(ri)
def floating_ip_added_dist(self, ri, fip, fip_cidr):
"""Add floating IP to FIP namespace."""
floating_ip = fip['floating_ip_address']
fixed_ip = fip['fixed_ip_address']
rule_pr = self.fip_priorities.pop()
rule_pr = ri.fip_ns.allocate_rule_priority()
ri.floating_ips_dict[floating_ip] = rule_pr
fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id)
fip_2_rtr_name = ri.fip_ns.get_int_device_name(ri.router_id)
ip_rule = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
ip_rule.add(fixed_ip, FIP_RT_TBL, rule_pr)
ip_rule.add(fixed_ip, dvr_fip_ns.FIP_RT_TBL, rule_pr)
#Add routing rule in fip namespace
fip_ns_name = self.get_fip_ns_name(str(fip['floating_network_id']))
fip_ns_name = ri.fip_ns.get_name()
rtr_2_fip, _ = ri.rtr_fip_subnet.get_pair()
device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper,
namespace=fip_ns_name)
device.route.add_route(fip_cidr, str(rtr_2_fip.ip))
interface_name = (
self.get_fip_ext_device_name(self.agent_gateway_port['id']))
ri.fip_ns.get_ext_device_name(ri.fip_ns.agent_gateway_port['id']))
ip_lib.send_garp_for_proxyarp(fip_ns_name,
interface_name,
floating_ip,
@ -378,17 +196,17 @@ class AgentMixin(object):
def floating_ip_removed_dist(self, ri, fip_cidr):
"""Remove floating IP from FIP namespace."""
floating_ip = fip_cidr.split('/')[0]
rtr_2_fip_name = self.get_rtr_int_device_name(ri.router_id)
fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id)
rtr_2_fip_name = ri.fip_ns.get_rtr_ext_device_name(ri.router_id)
fip_2_rtr_name = ri.fip_ns.get_int_device_name(ri.router_id)
if ri.rtr_fip_subnet is None:
ri.rtr_fip_subnet = self.local_subnets.allocate(ri.router_id)
rtr_2_fip, fip_2_rtr = ri.rtr_fip_subnet.get_pair()
fip_ns_name = self.get_fip_ns_name(str(self._fetch_external_net_id()))
fip_ns_name = ri.fip_ns.get_name()
if floating_ip in ri.floating_ips_dict:
rule_pr = ri.floating_ips_dict[floating_ip]
ip_rule = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
ip_rule.delete(floating_ip, FIP_RT_TBL, rule_pr)
self.fip_priorities.add(rule_pr)
ip_rule.delete(floating_ip, dvr_fip_ns.FIP_RT_TBL, rule_pr)
ri.fip_ns.deallocate_rule_priority(rule_pr)
#TODO(rajeev): Handle else case - exception/log?
device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper,
@ -403,11 +221,11 @@ class AgentMixin(object):
namespace=ri.ns_name)
ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=fip_ns_name)
device.route.delete_gateway(str(fip_2_rtr.ip),
table=FIP_RT_TBL)
self.local_subnets.release(ri.router_id)
table=dvr_fip_ns.FIP_RT_TBL)
ri.fip_ns.local_subnets.release(ri.router_id)
ri.rtr_fip_subnet = None
ns_ip.del_veth(fip_2_rtr_name)
is_last = self._fip_ns_unsubscribe(ri.router_id)
is_last = ri.fip_ns.unsubscribe(ri.router_id)
# clean up fip-namespace if this is the last FIP
if is_last:
self._destroy_fip_namespace(fip_ns_name)

View File

@ -0,0 +1,238 @@
# Copyright (c) 2015 Openstack Foundation
#
# 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.
import netaddr
import os
from neutron.agent.l3 import link_local_allocator as lla
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__)
FIP_NS_PREFIX = 'fip-'
FIP_EXT_DEV_PREFIX = 'fg-'
FIP_2_ROUTER_DEV_PREFIX = 'fpr-'
ROUTER_2_FIP_DEV_PREFIX = 'rfp-'
# Route Table index for FIPs
FIP_RT_TBL = 16
FIP_LL_SUBNET = '169.254.30.0/23'
# Rule priority range for FIPs
FIP_PR_START = 32768
FIP_PR_END = FIP_PR_START + 40000
class FipNamespace(object):
def __init__(self, ext_net_id, agent_conf, driver, root_helper, use_ipv6):
self._ext_net_id = ext_net_id
self.agent_conf = agent_conf
self.driver = driver
self.root_helper = root_helper
self.use_ipv6 = use_ipv6
self.agent_gateway_port = None
self._subscribers = set()
self._rule_priorities = set(range(FIP_PR_START, FIP_PR_END))
self._iptables_manager = iptables_manager.IptablesManager(
root_helper=self.root_helper,
namespace=self.get_name(),
use_ipv6=self.use_ipv6)
path = os.path.join(agent_conf.state_path, 'fip-linklocal-networks')
self.local_subnets = lla.LinkLocalAllocator(path, FIP_LL_SUBNET)
def get_name(self):
return (FIP_NS_PREFIX + self._ext_net_id)
def get_ext_device_name(self, port_id):
return (FIP_EXT_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
def get_int_device_name(self, router_id):
return (FIP_2_ROUTER_DEV_PREFIX + router_id)[:self.driver.DEV_NAME_LEN]
def get_rtr_ext_device_name(self, router_id):
return (ROUTER_2_FIP_DEV_PREFIX + router_id)[:self.driver.DEV_NAME_LEN]
def has_subscribers(self):
return len(self._subscribers) != 0
def subscribe(self, router_id):
is_first = not self.has_subscribers()
self._subscribers.add(router_id)
return is_first
def unsubscribe(self, router_id):
self._subscribers.discard(router_id)
return not self.has_subscribers()
def allocate_rule_priority(self):
return self._rule_priorities.pop()
def deallocate_rule_priority(self, rule_pr):
self._rule_priorities.add(rule_pr)
def _gateway_added(self, ex_gw_port, interface_name):
"""Add Floating IP gateway port."""
ns_name = self.get_name()
if not ip_lib.device_exists(interface_name,
root_helper=self.root_helper,
namespace=ns_name):
self.driver.plug(ex_gw_port['network_id'],
ex_gw_port['id'],
interface_name,
ex_gw_port['mac_address'],
bridge=self.agent_conf.external_network_bridge,
namespace=ns_name,
prefix=FIP_EXT_DEV_PREFIX)
self.driver.init_l3(interface_name,
[ex_gw_port['ip_cidr']],
namespace=ns_name)
ip_address = str(netaddr.IPNetwork(ex_gw_port['ip_cidr']).ip)
ip_lib.send_gratuitous_arp(ns_name,
interface_name,
ip_address,
self.agent_conf.send_arp_for_ha,
self.root_helper)
gw_ip = ex_gw_port['subnet']['gateway_ip']
if gw_ip:
ipd = ip_lib.IPDevice(interface_name,
self.root_helper,
namespace=ns_name)
ipd.route.add_gateway(gw_ip)
cmd = ['sysctl', '-w', 'net.ipv4.conf.%s.proxy_arp=1' % interface_name]
# TODO(Carl) mlavelle's work has self.ip_wrapper
ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns_name)
ip_wrapper.netns.execute(cmd, check_exit_code=False)
def create(self):
# TODO(Carl) Get this functionality from mlavelle's namespace baseclass
ip_wrapper_root = ip_lib.IPWrapper(self.root_helper)
ip_wrapper = ip_wrapper_root.ensure_namespace(self.get_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'])
# no connection tracking needed in fip namespace
self._iptables_manager.ipv4['raw'].add_rule('PREROUTING',
'-j CT --notrack')
self._iptables_manager.apply()
def destroy(self):
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(self.root_helper, namespace=ns)
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
ip_wrapper.del_veth(d.name)
elif d.name.startswith(FIP_EXT_DEV_PREFIX):
# single port from FIP NS to br-ext
# TODO(carl) Where does the port get deleted?
LOG.debug('DVR: unplug: %s', d.name)
ext_net_bridge = self.agent_conf.external_network_bridge
self.driver.unplug(d.name,
bridge=ext_net_bridge,
namespace=ns,
prefix=FIP_EXT_DEV_PREFIX)
LOG.debug('DVR: destroy fip ns: %s', ns)
# TODO(mrsmith): add LOG warn if fip count != 0
if self.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
def create_gateway_port(self, agent_gateway_port):
"""Create Floating IP gateway port.
Request port creation from Plugin then creates
Floating IP namespace and adds gateway port.
"""
self.agent_gateway_port = agent_gateway_port
# add fip-namespace and agent_gateway_port
self.create()
iface_name = self.get_ext_device_name(agent_gateway_port['id'])
self._gateway_added(agent_gateway_port, iface_name)
def _internal_ns_interface_added(self, ip_cidr,
interface_name, ns_name):
ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns_name)
ip_wrapper.netns.execute(['ip', 'addr', 'add',
ip_cidr, 'dev', interface_name])
def create_rtr_2_fip_link(self, ri):
"""Create interface between router and Floating IP namespace."""
rtr_2_fip_name = self.get_rtr_ext_device_name(ri.router_id)
fip_2_rtr_name = self.get_int_device_name(ri.router_id)
fip_ns_name = self.get_name()
# add link local IP to interface
if ri.rtr_fip_subnet is None:
ri.rtr_fip_subnet = self.local_subnets.allocate(ri.router_id)
rtr_2_fip, fip_2_rtr = ri.rtr_fip_subnet.get_pair()
ip_wrapper = ip_lib.IPWrapper(self.root_helper,
namespace=ri.ns_name)
device_exists = ip_lib.device_exists(rtr_2_fip_name,
self.root_helper,
namespace=ri.ns_name)
if not device_exists:
int_dev = ip_wrapper.add_veth(rtr_2_fip_name,
fip_2_rtr_name,
fip_ns_name)
self._internal_ns_interface_added(str(rtr_2_fip),
rtr_2_fip_name,
ri.ns_name)
self._internal_ns_interface_added(str(fip_2_rtr),
fip_2_rtr_name,
fip_ns_name)
int_dev[0].link.set_up()
int_dev[1].link.set_up()
# add default route for the link local interface
device = ip_lib.IPDevice(rtr_2_fip_name,
self.root_helper,
namespace=ri.ns_name)
device.route.add_gateway(str(fip_2_rtr.ip), table=FIP_RT_TBL)
#setup the NAT rules and chains
ri._handle_fip_nat_rules(rtr_2_fip_name, 'add_rules')
def scan_fip_ports(self, ri):
# don't scan if not dvr or count is not None
if ri.dist_fip_count is not None:
return
# scan system for any existing fip ports
ri.dist_fip_count = 0
rtr_2_fip_interface = self.get_rtr_ext_device_name(ri.router_id)
if ip_lib.device_exists(rtr_2_fip_interface,
root_helper=self.root_helper,
namespace=ri.ns_name):
device = ip_lib.IPDevice(rtr_2_fip_interface,
self.root_helper,
namespace=ri.ns_name)
existing_cidrs = [addr['cidr'] for addr in device.addr.list()]
fip_cidrs = [c for c in existing_cidrs if
common_utils.is_cidr_host(c)]
ri.dist_fip_count = len(fip_cidrs)

View File

@ -24,3 +24,26 @@ class DvrRouter(router.RouterInfo):
# Linklocal subnet for router and floating IP namespace link
self.rtr_fip_subnet = None
self.dist_fip_count = None
def _handle_fip_nat_rules(self, interface_name, action):
"""Configures NAT rules for Floating IPs for DVR.
Remove all the rules. This is safe because if
use_namespaces is set as False then the agent can
only configure one router, otherwise each router's
NAT rules will be in their own namespace.
"""
self.iptables_manager.ipv4['nat'].empty_chain('POSTROUTING')
self.iptables_manager.ipv4['nat'].empty_chain('snat')
# Add back the jump to float-snat
self.iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat')
# And add them back if the action is add_rules
if action == 'add_rules' and interface_name:
rule = ('POSTROUTING', '! -i %(interface_name)s '
'! -o %(interface_name)s -m conntrack ! '
'--ctstate DNAT -j ACCEPT' %
{'interface_name': interface_name})
self.iptables_manager.ipv4['nat'].add_rule(*rule)
self.iptables_manager.apply()

View File

@ -681,17 +681,16 @@ class TestDvrRouter(L3AgentTestFramework):
self.assertTrue(floating_ips)
external_port = self.agent._get_ex_gw_port(router)
fip_ns_name = (
self.agent.get_fip_ns_name(floating_ips[0]['floating_network_id'])
)
fip_ns = self.agent.get_fip_ns(floating_ips[0]['floating_network_id'])
fip_ns_name = fip_ns.get_name()
fg_port_created_succesfully = ip_lib.device_exists_with_ip_mac(
self.agent.get_fip_ext_device_name(external_port['id']),
fip_ns.get_ext_device_name(external_port['id']),
external_port['ip_cidr'],
external_port['mac_address'],
fip_ns_name, self.root_helper)
self.assertTrue(fg_port_created_succesfully)
# Check fpr-router device has been created
device_name = self.agent.get_fip_int_device_name(router.router_id)
device_name = fip_ns.get_int_device_name(router.router_id)
fpr_router_device_created_succesfully = ip_lib.device_exists(
device_name, self.root_helper, fip_ns_name)
self.assertTrue(fpr_router_device_created_succesfully)
@ -699,6 +698,6 @@ class TestDvrRouter(L3AgentTestFramework):
# In the router namespace
# Check rfp-<router-id> is created correctly
for fip in floating_ips:
device_name = self.agent.get_rtr_int_device_name(router.router_id)
device_name = fip_ns.get_rtr_ext_device_name(router.router_id)
self.assertTrue(ip_lib.device_exists(
device_name, self.root_helper, router.ns_name))

View File

@ -0,0 +1,188 @@
# Copyright (c) 2015 Openstack Foundation
#
# 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.
import mock
from neutron.agent.l3 import dvr_fip_ns
from neutron.agent.l3 import link_local_allocator as lla
from neutron.agent.linux import ip_lib
from neutron.openstack.common import uuidutils
from neutron.tests import base
_uuid = uuidutils.generate_uuid
class TestDvrFipNs(base.BaseTestCase):
def setUp(self):
super(TestDvrFipNs, self).setUp()
self.conf = mock.Mock()
self.conf.state_path = '/tmp'
self.driver = mock.Mock()
self.driver.DEV_NAME_LEN = 14
self.net_id = _uuid()
self.fip_ns = dvr_fip_ns.FipNamespace(self.net_id,
self.conf,
self.driver,
mock.sentinel.root_helper,
use_ipv6=True)
def test_subscribe(self):
is_first = self.fip_ns.subscribe(mock.sentinel.router_id)
self.assertTrue(is_first)
def test_subscribe_not_first(self):
self.fip_ns.subscribe(mock.sentinel.router_id)
is_first = self.fip_ns.subscribe(mock.sentinel.router_id2)
self.assertFalse(is_first)
def test_unsubscribe(self):
self.fip_ns.subscribe(mock.sentinel.router_id)
is_last = self.fip_ns.unsubscribe(mock.sentinel.router_id)
self.assertTrue(is_last)
def test_unsubscribe_not_last(self):
self.fip_ns.subscribe(mock.sentinel.router_id)
self.fip_ns.subscribe(mock.sentinel.router_id2)
is_last = self.fip_ns.unsubscribe(mock.sentinel.router_id2)
self.assertFalse(is_last)
def test_allocate_rule_priority(self):
pr = self.fip_ns.allocate_rule_priority()
self.assertNotIn(pr, self.fip_ns._rule_priorities)
def test_deallocate_rule_priority(self):
pr = self.fip_ns.allocate_rule_priority()
self.fip_ns.deallocate_rule_priority(pr)
self.assertIn(pr, self.fip_ns._rule_priorities)
@mock.patch.object(ip_lib, 'IPWrapper')
@mock.patch.object(ip_lib, 'IPDevice')
@mock.patch.object(ip_lib, 'send_gratuitous_arp')
@mock.patch.object(ip_lib, 'device_exists')
def test_gateway_added(self, device_exists, send_arp, IPDevice, IPWrapper):
agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
'subnet_id': _uuid()}],
'subnet': {'gateway_ip': '20.0.0.1'},
'id': _uuid(),
'network_id': self.net_id,
'mac_address': 'ca:fe:de:ad:be:ef',
'ip_cidr': '20.0.0.30/24'}
device_exists.return_value = False
self.fip_ns._gateway_added(agent_gw_port,
mock.sentinel.interface_name)
self.assertEqual(self.driver.plug.call_count, 1)
self.assertEqual(self.driver.init_l3.call_count, 1)
send_arp.assert_called_once_with(self.fip_ns.get_name(),
mock.sentinel.interface_name,
'20.0.0.30',
mock.ANY, mock.ANY)
@mock.patch.object(ip_lib, 'IPWrapper')
def test_destroy(self, IPWrapper):
ip_wrapper = IPWrapper()
dev1 = mock.Mock()
dev1.name = 'fpr-aaaa'
dev2 = mock.Mock()
dev2.name = 'fg-aaaa'
ip_wrapper.get_devices.return_value = [dev1, dev2]
self.fip_ns.destroy()
ext_net_bridge = self.conf.external_network_bridge
ns_name = self.fip_ns.get_name()
self.driver.unplug.assert_called_once_with('fg-aaaa',
bridge=ext_net_bridge,
prefix='fg-',
namespace=ns_name)
ip_wrapper.del_veth.assert_called_once_with('fpr-aaaa')
@mock.patch.object(ip_lib, 'IPWrapper')
@mock.patch.object(ip_lib, 'IPDevice')
@mock.patch.object(ip_lib, 'device_exists')
def test_create_rtr_2_fip_link(self, device_exists, IPDevice, IPWrapper):
ri = mock.Mock()
ri.router_id = _uuid()
ri.rtr_fip_subnet = None
ri.ns_name = mock.sentinel.router_ns
rtr_2_fip_name = self.fip_ns.get_rtr_ext_device_name(ri.router_id)
fip_2_rtr_name = self.fip_ns.get_int_device_name(ri.router_id)
fip_ns_name = self.fip_ns.get_name()
self.fip_ns.local_subnets = allocator = mock.Mock()
pair = lla.LinkLocalAddressPair('169.254.31.28/31')
allocator.allocate.return_value = pair
device_exists.return_value = False
self.fip_ns.create_rtr_2_fip_link(ri)
ip_wrapper = IPWrapper()
ip_wrapper.add_veth.assert_called_with(rtr_2_fip_name,
fip_2_rtr_name,
fip_ns_name)
device = IPDevice()
device.route.add_gateway.assert_called_once_with(
'169.254.31.29', table=16)
@mock.patch.object(ip_lib, 'IPWrapper')
@mock.patch.object(ip_lib, 'IPDevice')
@mock.patch.object(ip_lib, 'device_exists')
def test_create_rtr_2_fip_link_already_exists(self,
device_exists,
IPDevice,
IPWrapper):
ri = mock.Mock()
ri.router_id = _uuid()
ri.rtr_fip_subnet = None
device_exists.return_value = True
self.fip_ns.local_subnets = allocator = mock.Mock()
pair = lla.LinkLocalAddressPair('169.254.31.28/31')
allocator.allocate.return_value = pair
self.fip_ns.create_rtr_2_fip_link(ri)
ip_wrapper = IPWrapper()
self.assertFalse(ip_wrapper.add_veth.called)
@mock.patch.object(ip_lib, 'IPDevice')
def _test_scan_fip_ports(self, ri, ip_list, IPDevice):
IPDevice.return_value = device = mock.Mock()
device.addr.list.return_value = ip_list
self.fip_ns.get_rtr_ext_device_name = mock.Mock(
return_value=mock.sentinel.rtr_ext_device_name)
self.fip_ns.scan_fip_ports(ri)
@mock.patch.object(ip_lib, 'device_exists')
def test_scan_fip_ports_restart_fips(self, device_exists):
device_exists.return_value = True
ri = mock.Mock()
ri.dist_fip_count = None
ip_list = [{'cidr': '111.2.3.4/32'}, {'cidr': '111.2.3.5/32'}]
self._test_scan_fip_ports(ri, ip_list)
self.assertEqual(2, ri.dist_fip_count)
@mock.patch.object(ip_lib, 'device_exists')
def test_scan_fip_ports_restart_none(self, device_exists):
device_exists.return_value = True
ri = mock.Mock()
ri.dist_fip_count = None
self._test_scan_fip_ports(ri, [])
self.assertEqual(0, ri.dist_fip_count)
def test_scan_fip_ports_restart_zero(self):
ri = mock.Mock()
ri.dist_fip_count = 0
self._test_scan_fip_ports(ri, None)
self.assertEqual(0, ri.dist_fip_count)

View File

@ -26,6 +26,7 @@ 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_fip_ns
from neutron.agent.l3 import dvr_router
from neutron.agent.l3 import ha
from neutron.agent.l3 import link_local_allocator as lla
@ -415,19 +416,21 @@ class TestBasicRouterOperations(base.BaseTestCase):
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'):
agent.conf.agent_mode = 'dvr_snat'
agent.host = HOSTNAME
agent._create_dvr_gateway = mock.Mock()
agent.get_snat_interfaces = mock.Mock(return_value=self.snat_ports)
ri.fip_ns = agent.get_fip_ns(ex_net_id)
ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
'subnet_id': _uuid()}],
'subnet': {'gateway_ip': '20.0.0.1'},
'extra_subnets': [{'cidr': '172.16.0.0/24'}],
'id': _uuid(),
'network_id': _uuid(),
'network_id': ex_net_id,
'mac_address': 'ca:fe:de:ad:be:ef',
'ip_cidr': '20.0.0.30/24'}
interface_name = agent.get_external_device_name(ex_gw_port['id'])
@ -825,39 +828,6 @@ class TestBasicRouterOperations(base.BaseTestCase):
4, '1.5.25.15', '00:44:33:22:11:55')
agent.router_deleted(None, router['id'])
@mock.patch('neutron.agent.linux.ip_lib.IPDevice')
def _test_scan_fip_ports(self, ri, ip_list, IPDevice):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
self.device_exists.return_value = True
IPDevice.return_value = device = mock.Mock()
device.addr.list.return_value = ip_list
agent.scan_fip_ports(ri)
def test_scan_fip_ports_restart_fips(self):
router = prepare_router_data()
ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
ri.router['distributed'] = True
ip_list = [{'cidr': '111.2.3.4/32'}, {'cidr': '111.2.3.5/32'}]
self._test_scan_fip_ports(ri, ip_list)
self.assertEqual(ri.dist_fip_count, 2)
def test_scan_fip_ports_restart_none(self):
router = prepare_router_data()
ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
ri.router['distributed'] = True
ip_list = []
self._test_scan_fip_ports(ri, ip_list)
self.assertEqual(ri.dist_fip_count, 0)
def test_scan_fip_ports_restart_zero(self):
router = prepare_router_data()
ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
ri.router['distributed'] = True
ri.dist_fip_count = 0
ip_list = None
self._test_scan_fip_ports(ri, ip_list)
self.assertEqual(ri.dist_fip_count, 0)
def test_process_cent_router(self):
router = prepare_router_data()
ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
@ -961,10 +931,11 @@ class TestBasicRouterOperations(base.BaseTestCase):
IPDevice.return_value = device = mock.Mock()
device.addr.list.return_value = []
ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
ex_gw_port = {'id': _uuid()}
ex_gw_port = {'id': _uuid(), 'network_id': mock.sentinel.ext_net_id}
with mock.patch.object(lla.LinkLocalAllocator, '_write'):
if ri.router['distributed']:
ri.fip_ns = agent.get_fip_ns(ex_gw_port['network_id'])
agent.create_dvr_fip_interfaces(ri, ex_gw_port)
fip_statuses = agent.process_router_floating_ip_addresses(
ri, ex_gw_port)
@ -1015,13 +986,13 @@ class TestBasicRouterOperations(base.BaseTestCase):
'host': HOSTNAME,
'floating_ip_address': '15.1.2.3',
'fixed_ip_address': '192.168.0.1',
'floating_network_id': _uuid(),
'floating_network_id': mock.sentinel.ext_net_id,
'port_id': _uuid()},
{'id': _uuid(),
'host': 'some-other-host',
'floating_ip_address': '15.1.2.4',
'fixed_ip_address': '192.168.0.10',
'floating_network_id': _uuid(),
'floating_network_id': mock.sentinel.ext_net_id,
'port_id': _uuid()}]}
router = prepare_router_data(enable_snat=True)
@ -1032,7 +1003,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
ri.dist_fip_count = 0
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
agent.host = HOSTNAME
agent.agent_gateway_port = (
fip_ns = agent.get_fip_ns(mock.sentinel.ext_net_id)
fip_ns.agent_gateway_port = (
{'fixed_ips': [{'ip_address': '20.0.0.30',
'subnet_id': _uuid()}],
'subnet': {'gateway_ip': '20.0.0.1'},
@ -1140,7 +1112,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
fip_statuses = agent.process_router_floating_ip_addresses(
ri, {'id': _uuid()})
ri, {'id': _uuid(), 'network_id': mock.sentinel.ext_net_id})
self.assertIsNone(fip_statuses.get(fip_id))
@ -1617,23 +1589,6 @@ class TestBasicRouterOperations(base.BaseTestCase):
agent.router_added_to_agent(None, [FAKE_ID])
self.assertEqual(1, agent._queue.add.call_count)
def test_destroy_fip_namespace(self):
namespaces = ['qrouter-foo', 'qrouter-bar']
self.mock_ip.get_namespaces.return_value = namespaces
self.mock_ip.get_devices.return_value = [FakeDev('fpr-aaaa'),
FakeDev('fg-aaaa')]
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
agent._destroy_fip_namespace(namespaces[0])
self.mock_driver.unplug.assert_called_once_with('fg-aaaa',
bridge='br-ex',
prefix='fg-',
namespace='qrouter'
'-foo')
self.mock_ip.del_veth.assert_called_once_with('fpr-aaaa')
def test_destroy_namespace(self):
namespace = 'qrouter-bar'
@ -1910,81 +1865,16 @@ class TestBasicRouterOperations(base.BaseTestCase):
self.assertEqual(self.mock_driver.plug.call_count, 3)
self.assertEqual(self.mock_driver.init_l3.call_count, 3)
def test_agent_gateway_added(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
network_id = _uuid()
port_id = _uuid()
agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
'subnet_id': _uuid()}],
'subnet': {'gateway_ip': '20.0.0.1'},
'id': port_id,
'network_id': network_id,
'mac_address': 'ca:fe:de:ad:be:ef',
'ip_cidr': '20.0.0.30/24'}
fip_ns_name = (
agent.get_fip_ns_name(str(network_id)))
interface_name = (
agent.get_fip_ext_device_name(port_id))
self.device_exists.return_value = False
agent.agent_gateway_added(fip_ns_name, agent_gw_port,
interface_name)
self.assertEqual(self.mock_driver.plug.call_count, 1)
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
if self.conf.use_namespaces:
self.send_arp.assert_called_once_with(fip_ns_name, interface_name,
'20.0.0.30',
mock.ANY, mock.ANY)
else:
self.utils_exec.assert_any_call(
check_exit_code=True, root_helper=self.conf.root_helper)
def test_create_rtr_2_fip_link(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router = prepare_router_data()
fip = {'id': _uuid(),
'host': HOSTNAME,
'floating_ip_address': '15.1.2.3',
'fixed_ip_address': '192.168.0.1',
'floating_network_id': _uuid(),
'port_id': _uuid()}
ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
rtr_2_fip_name = agent.get_rtr_int_device_name(ri.router_id)
fip_2_rtr_name = agent.get_fip_int_device_name(ri.router_id)
fip_ns_name = agent.get_fip_ns_name(str(fip['floating_network_id']))
with mock.patch.object(lla.LinkLocalAllocator, '_write'):
self.device_exists.return_value = False
agent.create_rtr_2_fip_link(ri, fip['floating_network_id'])
self.mock_ip.add_veth.assert_called_with(rtr_2_fip_name,
fip_2_rtr_name, fip_ns_name)
# TODO(mrsmith): add more aasserts -
self.mock_ip_dev.route.add_gateway.assert_called_once_with(
'169.254.31.29', table=16)
# TODO(mrsmith): test _create_agent_gateway_port
def test_create_rtr_2_fip_link_already_exists(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router = prepare_router_data()
ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
self.device_exists.return_value = True
with mock.patch.object(lla.LinkLocalAllocator, '_write'):
agent.create_rtr_2_fip_link(ri, {})
self.assertFalse(self.mock_ip.add_veth.called)
def test_floating_ip_added_dist(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router = prepare_router_data()
ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
ext_net_id = _uuid()
agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
'subnet_id': _uuid()}],
'subnet': {'gateway_ip': '20.0.0.1'},
'id': _uuid(),
'network_id': _uuid(),
'network_id': ext_net_id,
'mac_address': 'ca:fe:de:ad:be:ef',
'ip_cidr': '20.0.0.30/24'}
@ -1992,9 +1882,10 @@ class TestBasicRouterOperations(base.BaseTestCase):
'host': HOSTNAME,
'floating_ip_address': '15.1.2.3',
'fixed_ip_address': '192.168.0.1',
'floating_network_id': _uuid(),
'floating_network_id': ext_net_id,
'port_id': _uuid()}
agent.agent_gateway_port = agent_gw_port
ri.fip_ns = agent.get_fip_ns(ext_net_id)
ri.fip_ns.agent_gateway_port = agent_gw_port
ri.rtr_fip_subnet = lla.LinkLocalAddressPair('169.254.30.42/31')
ri.dist_fip_count = 0
ip_cidr = common_utils.ip_to_cidr(fip['floating_ip_address'])
@ -2002,9 +1893,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
self.mock_rule.add.assert_called_with('192.168.0.1', 16, FIP_PRI)
# TODO(mrsmith): add more asserts
@mock.patch.object(l3_agent.L3NATAgent, '_fip_ns_unsubscribe')
@mock.patch.object(lla.LinkLocalAllocator, '_write')
def test_floating_ip_removed_dist(self, write, unsubscribe):
def test_floating_ip_removed_dist(self, write):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router = prepare_router_data()
agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
@ -2018,7 +1908,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
ri.dist_fip_count = 2
agent.fip_ns_subscribers.add(ri.router_id)
ri.fip_ns = agent.get_fip_ns(agent._fetch_external_net_id())
ri.fip_ns.unsubscribe = mock.Mock()
ri.floating_ips_dict['11.22.33.44'] = FIP_PRI
ri.fip_2_rtr = '11.22.33.42'
ri.rtr_2_fip = '11.22.33.40'
@ -2030,21 +1921,20 @@ class TestBasicRouterOperations(base.BaseTestCase):
self.mock_rule.delete.assert_called_with(floating_ip, 16, FIP_PRI)
self.mock_ip_dev.route.delete_route.assert_called_with(fip_cidr,
str(s.ip))
self.assertFalse(unsubscribe.called, '_fip_ns_unsubscribe called!')
self.assertFalse(ri.fip_ns.unsubscribe.called)
with mock.patch.object(agent, '_destroy_fip_namespace') as f:
ri.dist_fip_count = 1
fip_ns_name = agent.get_fip_ns_name(
str(agent._fetch_external_net_id()))
ri.rtr_fip_subnet = agent.local_subnets.allocate(ri.router_id)
fip_ns_name = ri.fip_ns.get_name()
ri.rtr_fip_subnet = ri.fip_ns.local_subnets.allocate(ri.router_id)
_, fip_to_rtr = ri.rtr_fip_subnet.get_pair()
agent.floating_ip_removed_dist(ri, fip_cidr)
self.mock_ip.del_veth.assert_called_once_with(
agent.get_fip_int_device_name(router['id']))
ri.fip_ns.get_int_device_name(router['id']))
self.mock_ip_dev.route.delete_gateway.assert_called_once_with(
str(fip_to_rtr.ip), table=16)
f.assert_called_once_with(fip_ns_name)
unsubscribe.assert_called_once_with(ri.router_id)
ri.fip_ns.unsubscribe.assert_called_once_with(ri.router_id)
def test_get_service_plugin_list(self):
service_plugins = [p_const.L3_ROUTER_NAT]
@ -2080,74 +1970,41 @@ class TestBasicRouterOperations(base.BaseTestCase):
self.assertRaises(messaging.MessagingTimeout, l3_agent.L3NATAgent,
HOSTNAME, self.conf)
def test__fip_ns_subscribe_is_first_true(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router_id = _uuid()
is_first = agent._fip_ns_subscribe(router_id)
self.assertTrue(is_first)
self.assertEqual(len(agent.fip_ns_subscribers), 1)
def test__fip_ns_subscribe_is_first_false(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router_id = _uuid()
router2_id = _uuid()
agent._fip_ns_subscribe(router_id)
is_first = agent._fip_ns_subscribe(router2_id)
self.assertFalse(is_first)
self.assertEqual(len(agent.fip_ns_subscribers), 2)
def test__fip_ns_unsubscribe_is_last_true(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router_id = _uuid()
agent.fip_ns_subscribers.add(router_id)
is_last = agent._fip_ns_unsubscribe(router_id)
self.assertTrue(is_last)
self.assertEqual(len(agent.fip_ns_subscribers), 0)
def test__fip_ns_unsubscribe_is_last_false(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router_id = _uuid()
router2_id = _uuid()
agent.fip_ns_subscribers.add(router_id)
agent.fip_ns_subscribers.add(router2_id)
is_last = agent._fip_ns_unsubscribe(router_id)
self.assertFalse(is_last)
self.assertEqual(len(agent.fip_ns_subscribers), 1)
def test_external_gateway_removed_ext_gw_port_and_fip(self):
self.conf.set_override('state_path', '/tmp')
self.conf.set_override('router_delete_namespaces', True)
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
agent.conf.agent_mode = 'dvr'
agent.agent_gateway_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
'subnet_id': _uuid()}],
'subnet': {'gateway_ip': '20.0.0.1'},
'id': _uuid(),
'network_id': _uuid(),
'mac_address': 'ca:fe:de:ad:be:ef',
'ip_cidr': '20.0.0.30/24'}
external_net_id = _uuid()
agent._fetch_external_net_id = mock.Mock(return_value=external_net_id)
router = prepare_router_data(num_internal_ports=2)
router['distributed'] = True
router['gw_port_host'] = HOSTNAME
external_net_id = router['gw_port']['network_id']
ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs)
ri.fip_ns = agent.get_fip_ns(external_net_id)
ri.fip_ns.agent_gateway_port = {
'fixed_ips': [{'ip_address': '20.0.0.30', 'subnet_id': _uuid()}],
'subnet': {'gateway_ip': '20.0.0.1'},
'id': _uuid(),
'network_id': external_net_id,
'mac_address': 'ca:fe:de:ad:be:ef',
'ip_cidr': '20.0.0.30/24'}
agent._fetch_external_net_id = mock.Mock(return_value=external_net_id)
vm_floating_ip = '19.4.4.2'
ri.floating_ips_dict[vm_floating_ip] = FIP_PRI
ri.dist_fip_count = 1
ri.ex_gw_port = ri.router['gw_port']
del ri.router['gw_port']
ri.rtr_fip_subnet = agent.local_subnets.allocate(ri.router_id)
ri.rtr_fip_subnet = ri.fip_ns.local_subnets.allocate(ri.router_id)
_, fip_to_rtr = ri.rtr_fip_subnet.get_pair()
nat = ri.iptables_manager.ipv4['nat']
nat.clear_rules_by_tag = mock.Mock()
nat.add_rule = mock.Mock()
self.mock_ip.get_devices.return_value = [
FakeDev(agent.get_fip_ext_device_name(_uuid()))]
FakeDev(ri.fip_ns.get_ext_device_name(_uuid()))]
self.mock_ip_dev.addr.list.return_value = [
{'cidr': vm_floating_ip + '/32'},
{'cidr': '19.4.4.1/24'}]
@ -2158,16 +2015,15 @@ class TestBasicRouterOperations(base.BaseTestCase):
agent.get_external_device_name(ri.ex_gw_port['id']))
self.mock_ip.del_veth.assert_called_once_with(
agent.get_fip_int_device_name(ri.router['id']))
ri.fip_ns.get_int_device_name(ri.router['id']))
self.mock_ip_dev.route.delete_gateway.assert_called_once_with(
str(fip_to_rtr.ip), table=dvr.FIP_RT_TBL)
str(fip_to_rtr.ip), table=dvr_fip_ns.FIP_RT_TBL)
self.assertEqual(ri.dist_fip_count, 0)
self.assertEqual(len(agent.fip_ns_subscribers), 0)
self.assertFalse(ri.fip_ns.has_subscribers())
self.assertEqual(self.mock_driver.unplug.call_count, 1)
self.assertIsNone(agent.agent_gateway_port)
self.mock_ip.netns.delete.assert_called_once_with(
agent.get_fip_ns_name(external_net_id))
self.assertIsNone(ri.fip_ns.agent_gateway_port)
self.mock_ip.netns.delete.assert_called_once_with(ri.fip_ns.get_name())
self.assertFalse(nat.add_rule.called)
nat.clear_rules_by_tag.assert_called_once_with('floating_ip')