from neutron.agent.linux import interface
from neutron.agent.linux import ip_lib
from neutron.agent.linux import iptables_manager
-from neutron.agent.linux import ovs_lib # noqa
from neutron.agent.linux import ra
from neutron.agent import rpc as agent_rpc
from neutron.common import config as common_config
NS_PREFIX = 'qrouter-'
INTERNAL_DEV_PREFIX = 'qr-'
EXTERNAL_DEV_PREFIX = 'qg-'
+SNAT_INT_DEV_PREFIX = 'sg-'
+FIP_NS_PREFIX = 'fip-'
+SNAT_NS_PREFIX = 'snat-'
+FIP_2_ROUTER_DEV_PREFIX = 'fpr-'
+ROUTER_2_FIP_DEV_PREFIX = 'rfp-'
+FIP_EXT_DEV_PREFIX = 'fg-'
+FIP_LL_PREFIX = '169.254.30.'
+# Route Table index for FIPs
+FIP_RT_TBL = 16
+# Rule priority range for FIPs
+FIP_PR_START = 32768
+FIP_PR_END = FIP_PR_START + 40000
RPC_LOOP_INTERVAL = 1
FLOATING_IP_CIDR_SUFFIX = '/32'
# Lower value is higher priority
API version history:
1.0 - Initial version.
1.1 - Floating IP operational status updates
+ 1.2 - DVR support: new L3 plugin methods added.
+ - get_ports_by_subnet
+ - get_agent_gateway_port
+ Needed by the agent when operating in DVR/DVR_SNAT mode
"""
topic=self.topic,
version='1.1')
+ def get_ports_by_subnet(self, context, subnet_id):
+ """Retrieve ports by subnet id."""
+ return self.call(context,
+ self.make_msg('get_ports_by_subnet', host=self.host,
+ subnet_id=subnet_id),
+ topic=self.topic,
+ version='1.2')
+
+ def get_agent_gateway_port(self, context, fip_net):
+ """Get or create an agent_gateway_port."""
+ return self.call(context,
+ self.make_msg('get_agent_gateway_port',
+ network_id=fip_net, host=self.host),
+ topic=self.topic,
+ version='1.2')
+
class RouterInfo(object):
self._snat_enabled = None
self._snat_action = None
self.internal_ports = []
+ self.snat_ports = []
self.floating_ips = set()
+ self.floating_ips_dict = {}
self.root_helper = root_helper
self.use_namespaces = use_namespaces
# Invoke the setter for establishing initial SNAT action
#FIXME(danwent): use_ipv6=True,
namespace=self.ns_name)
self.routes = []
+ # DVR Data
+ # Linklocal router to floating IP addr
+ self.rtr_2_fip = None
+ # Linklocal floating to router IP addr
+ self.fip_2_rtr = None
+ self.dist_fip_count = 0
@property
def router(self):
It was previously a list of routers in dict format.
It is now a list of router IDs only.
Per rpc versioning rules, it is backwards compatible.
+ 1.2 - DVR support: new L3 agent methods added.
+ - add_arp_entry
+ - del_arp_entry
+ Needed by the L3 service when dealing with DVR
"""
- RPC_API_VERSION = '1.1'
+ RPC_API_VERSION = '1.2'
OPTS = [
+ cfg.StrOpt('agent_mode', default='legacy',
+ help=_("The working mode for the agent. Allowed modes are: "
+ "'legacy' - this preserves the existing behavior "
+ "where the L3 agent is deployed on a centralized "
+ "networking node to provide L3 services like DNAT, "
+ "and SNAT. Use this mode if you do not want to "
+ "adopt DVR. 'dvr' - this mode enables DVR "
+ "functionality and must be used for an L3 agent "
+ "that runs on a compute host. 'dvr_snat' - this "
+ "enables centralized SNAT support in conjunction "
+ "with DVR. This mode must be used for an L3 agent "
+ "running on a centralized node (or in single-host "
+ "deployments, e.g. devstack)")),
cfg.StrOpt('external_network_bridge', default='br-ex',
help=_("Name of bridge used for external network "
"traffic.")),
self._clean_stale_namespaces = self.conf.use_namespaces
+ # dvr data
+ self.agent_gateway_port = None
+ self.agent_fip_count = 0
+ self.local_ips = set(range(2, 251))
+ self.fip_priorities = set(range(FIP_PR_START, FIP_PR_END))
+
self._queue = RouterProcessingQueue()
super(L3NATAgent, self).__init__(conf=self.conf)
one attempt will be made to delete them.
"""
for ns in router_namespaces:
- if self.conf.enable_metadata_proxy:
- self._destroy_metadata_proxy(ns[len(NS_PREFIX):], ns)
-
ra.disable_ipv6_ra(ns[len(NS_PREFIX):], ns, self.root_helper)
try:
- self._destroy_router_namespace(ns)
+ self._destroy_namespace(ns)
except RuntimeError:
LOG.exception(_('Failed to destroy stale router namespace '
'%s'), ns)
self._clean_stale_namespaces = False
- def _destroy_router_namespace(self, namespace):
- ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=namespace)
+ def _destroy_namespace(self, ns):
+ if ns.startswith(NS_PREFIX):
+ if self.conf.enable_metadata_proxy:
+ self._destroy_metadata_proxy(ns[len(NS_PREFIX):], ns)
+ self._destroy_router_namespace(ns)
+ elif ns.startswith(FIP_NS_PREFIX):
+ self._destroy_fip_namespace(ns)
+ elif ns.startswith(SNAT_NS_PREFIX):
+ self._destroy_snat_namespace(ns)
+
+ def _delete_namespace(self, ns_ip, ns):
+ try:
+ ns_ip.netns.delete(ns)
+ except RuntimeError:
+ msg = _('Failed trying to delete namespace: %s') % ns
+ LOG.exception(msg)
+
+ def _destroy_snat_namespace(self, ns):
+ ns_ip = ip_lib.IPWrapper(self.root_helper, 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):
+ 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
+ # TODO(mrsmith): remove IR interfaces (IP pool?)
+ pass
+ elif d.name.startswith(FIP_EXT_DEV_PREFIX):
+ # single port from FIP NS to br-ext
+ # TODO(mrsmith): remove br-ext interface
+ 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
+
+ def _destroy_router_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(INTERNAL_DEV_PREFIX):
# device is on default bridge
- self.driver.unplug(d.name, namespace=namespace,
+ self.driver.unplug(d.name, namespace=ns,
prefix=INTERNAL_DEV_PREFIX)
elif d.name.startswith(EXTERNAL_DEV_PREFIX):
self.driver.unplug(d.name,
bridge=self.conf.external_network_bridge,
- namespace=namespace,
+ namespace=ns,
prefix=EXTERNAL_DEV_PREFIX)
if self.conf.router_delete_namespaces:
- try:
- ns_ip.netns.delete(namespace)
- except RuntimeError:
- msg = _('Failed trying to delete namespace: %s')
- LOG.exception(msg % namespace)
+ self._delete_namespace(ns_ip, ns)
+
+ def _create_namespace(self, name):
+ ip_wrapper_root = ip_lib.IPWrapper(self.root_helper)
+ ip_wrapper = ip_wrapper_root.ensure_namespace(name)
+ ip_wrapper.netns.execute(['sysctl', '-w', 'net.ipv4.ip_forward=1'])
def _create_router_namespace(self, ri):
- ip_wrapper_root = ip_lib.IPWrapper(self.root_helper)
- ip_wrapper = ip_wrapper_root.ensure_namespace(ri.ns_name)
- ip_wrapper.netns.execute(['sysctl', '-w', 'net.ipv4.ip_forward=1'])
+ self._create_namespace(ri.ns_name)
def _fetch_external_net_id(self, force=False):
"""Find UUID of single external network for this agent."""
ns_name)
pm.disable()
+ def _set_subnet_arp_info(self, ri, port):
+ """Set ARP info retrieved from Plugin for existing ports."""
+ if 'id' not in port['subnet'] or not ri.router['distributed']:
+ return
+ subnet_id = port['subnet']['id']
+ subnet_ports = (
+ self.plugin_rpc.get_ports_by_subnet(self.context,
+ subnet_id))
+
+ for p in subnet_ports:
+ if (p['device_owner'] not in (
+ l3_constants.DEVICE_OWNER_ROUTER_INTF,
+ l3_constants.DEVICE_OWNER_DVR_INTERFACE)):
+ for fixed_ip in p['fixed_ips']:
+ self._update_arp_entry(ri, fixed_ip['ip_address'],
+ p['mac_address'],
+ subnet_id, 'add')
+
def _set_subnet_info(self, port):
ips = port['fixed_ips']
if not ips:
return [ip_dev.name for ip_dev in ip_devs]
def process_router(self, ri):
+ # TODO(mrsmith) - we shouldn't need to check here
+ if 'distributed' not in ri.router:
+ ri.router['distributed'] = False
ri.iptables_manager.defer_apply_on()
ex_gw_port = self._get_ex_gw_port(ri)
internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
+ snat_ports = ri.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, [])
existing_port_ids = set([p['id'] for p in ri.internal_ports])
current_port_ids = set([p['id'] for p in internal_ports
if p['admin_state_up']])
old_ipv6_port = False
for p in new_ports:
self._set_subnet_info(p)
- self.internal_network_added(ri, p['network_id'], p['id'],
- p['ip_cidr'], p['mac_address'])
+ self.internal_network_added(ri, p)
ri.internal_ports.append(p)
+ self._set_subnet_arp_info(ri, p)
if (not new_ipv6_port and
netaddr.IPNetwork(p['subnet']['cidr']).version == 6):
new_ipv6_port = True
for p in old_ports:
- self.internal_network_removed(ri, p['id'], p['ip_cidr'])
+ self.internal_network_removed(ri, p)
ri.internal_ports.remove(p)
if (not old_ipv6_port and
netaddr.IPNetwork(p['subnet']['cidr']).version == 6):
# Process static routes for router
self.routes_updated(ri)
# Process SNAT rules for external gateway
- ri.perform_snat_action(self._handle_router_snat_rules,
- internal_cidrs, interface_name)
+ if (not ri.router['distributed'] or
+ ex_gw_port and ri.router['gw_port_host'] == self.host):
+ ri.perform_snat_action(self._handle_router_snat_rules,
+ internal_cidrs, interface_name)
# Process SNAT/DNAT rules for floating IPs
fip_statuses = {}
# Update ex_gw_port and enable_snat on the router info cache
ri.ex_gw_port = ex_gw_port
+ ri.snat_ports = snat_ports
ri.enable_snat = ri.router.get('enable_snat')
def _handle_router_snat_rules(self, ri, ex_gw_port, internal_cidrs,
# This is safe because if use_namespaces is set as False
# then the agent can only configure one router, otherwise
# each router's SNAT rules will be in their own namespace
- ri.iptables_manager.ipv4['nat'].empty_chain('POSTROUTING')
- ri.iptables_manager.ipv4['nat'].empty_chain('snat')
+ if ri.router['distributed']:
+ iptables_manager = ri.snat_iptables_manager
+ else:
+ iptables_manager = ri.iptables_manager
- # Add back the jump to float-snat
- ri.iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat')
+ iptables_manager.ipv4['nat'].empty_chain('POSTROUTING')
+ iptables_manager.ipv4['nat'].empty_chain('snat')
+
+ if not ri.router['distributed']:
+ # Add back the jump to float-snat
+ iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat')
- # And add them back if the action if add_rules
+ # And add them back if the action is add_rules
if action == 'add_rules' and ex_gw_port:
# ex_gw_port should not be None in this case
# NAT rules are added only if ex_gw_port has an IPv4 address
internal_cidrs,
interface_name)
for rule in rules:
- ri.iptables_manager.ipv4['nat'].add_rule(*rule)
+ iptables_manager.ipv4['nat'].add_rule(*rule)
break
+ iptables_manager.apply()
+
+ 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 process_router_floating_ip_nat_rules(self, ri):
# Clear out all iptables rules for floating ips
ri.iptables_manager.ipv4['nat'].clear_rules_by_tag('floating_ip')
+ floating_ips = self.get_floating_ips(ri)
# Loop once to ensure that floating ips are configured.
- for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []):
+ for fip in floating_ips:
# Rebuild iptables rules for the floating ip.
fixed = fip['fixed_ip_address']
fip_ip = fip['floating_ip_address']
those that should not longer be configured.
"""
fip_statuses = {}
- interface_name = self.get_external_device_name(ex_gw_port['id'])
+
+ floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
+ if ri.router['distributed']:
+ # filter out only FIPs for this host/agent
+ floating_ips = [i for i in floating_ips if i['host'] == self.host]
+ if floating_ips and self.agent_gateway_port is None:
+ self._create_agent_gateway_port(ri, floating_ips[0]
+ ['floating_network_id'])
+
+ if self.agent_gateway_port:
+ if floating_ips and ri.dist_fip_count == 0:
+ self.create_rtr_2_fip_link(ri, floating_ips[0]
+ ['floating_network_id'])
+ interface_name = self.get_rtr_int_device_name(ri.router_id)
+ else:
+ # there are no fips or agent port, no work to do
+ return fip_statuses
+ else:
+ interface_name = self.get_external_device_name(ex_gw_port['id'])
+
device = ip_lib.IPDevice(interface_name, self.root_helper,
namespace=ri.ns_name)
existing_cidrs = set([addr['cidr'] for addr in device.addr.list()])
new_cidrs = set()
# Loop once to ensure that floating ips are configured.
- for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []):
+ for fip in floating_ips:
fip_ip = fip['floating_ip_address']
ip_cidr = str(fip_ip) + FLOATING_IP_CIDR_SUFFIX
LOG.warn(_("Unable to configure IP address for "
"floating IP: %s"), fip['id'])
continue
- # As GARP is processed in a distinct thread the call below
- # won't raise an exception to be handled.
- self._send_gratuitous_arp_packet(
- ri, interface_name, fip_ip)
+ if ri.router['distributed']:
+ # Special Handling for DVR - update FIP namespace
+ # and ri.namespace to handle DVR based FIP
+ self.floating_ip_added_dist(ri, fip)
+ else:
+ # As GARP is processed in a distinct thread the call below
+ # won't raise an exception to be handled.
+ self._send_gratuitous_arp_packet(
+ ri.ns_name, interface_name, fip_ip)
fip_statuses[fip['id']] = (
l3_constants.FLOATINGIP_STATUS_ACTIVE)
if ip_cidr.endswith(FLOATING_IP_CIDR_SUFFIX):
net = netaddr.IPNetwork(ip_cidr)
device.addr.delete(net.version, ip_cidr)
+ if ri.router['distributed']:
+ self.floating_ip_removed_dist(ri, ip_cidr)
return fip_statuses
def _get_ex_gw_port(self, ri):
return ri.router.get('gw_port')
- def _arping(self, ri, interface_name, ip_address):
+ def _arping(self, ns_name, interface_name, ip_address, distributed=False):
+ if distributed:
+ device = ip_lib.IPDevice(interface_name, self.root_helper,
+ namespace=ns_name)
+ ip_cidr = str(ip_address) + FLOATING_IP_CIDR_SUFFIX
+ net = netaddr.IPNetwork(ip_cidr)
+ device.addr.add(net.version, ip_cidr, str(net.broadcast))
+
arping_cmd = ['arping', '-A',
'-I', interface_name,
'-c', self.conf.send_arp_for_ha,
ip_address]
try:
ip_wrapper = ip_lib.IPWrapper(self.root_helper,
- namespace=ri.ns_name)
+ namespace=ns_name)
ip_wrapper.netns.execute(arping_cmd, check_exit_code=True)
except Exception as e:
LOG.error(_("Failed sending gratuitous ARP: %s"), str(e))
+ if distributed:
+ device.addr.delete(net.version, ip_cidr)
- def _send_gratuitous_arp_packet(self, ri, interface_name, ip_address):
+ def _send_gratuitous_arp_packet(self, ns_name, interface_name, ip_address,
+ distributed=False):
if self.conf.send_arp_for_ha > 0:
- eventlet.spawn_n(self._arping, ri, interface_name, ip_address)
+ eventlet.spawn_n(self._arping, ns_name, interface_name, ip_address,
+ distributed)
+
+ def get_internal_port(self, ri, subnet_id):
+ """Return internal router port based on subnet_id."""
+ router_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
+ for port in router_ports:
+ fips = port['fixed_ips']
+ for f in fips:
+ if f['subnet_id'] == subnet_id:
+ return port
def get_internal_device_name(self, port_id):
return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
def get_external_device_name(self, port_id):
return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
+ def get_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)
+
+ def get_snat_interfaces(self, ri):
+ return ri.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, [])
+
+ def get_floating_ips(self, ri):
+ """Filter Floating IPs to be hosted on this agent."""
+ floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
+ if ri.router['distributed']:
+ floating_ips = [i for i in floating_ips if i['host'] == self.host]
+ return floating_ips
+
+ def _map_internal_interfaces(self, ri, int_port, snat_ports):
+ """Return the SNAT port for the given internal interface port."""
+ fixed_ip = int_port['fixed_ips'][0]
+ subnet_id = fixed_ip['subnet_id']
+ match_port = [p for p in snat_ports if
+ p['fixed_ips'][0]['subnet_id'] == subnet_id]
+ if match_port:
+ return match_port[0]
+ else:
+ LOG.error(_('DVR: no map match_port found!'))
+
+ def _create_dvr_gateway(self, ri, ex_gw_port, gw_interface_name,
+ internal_cidrs, snat_ports):
+ """Create SNAT namespace."""
+ snat_ns_name = self.get_snat_ns_name(ri.router['id'])
+ self._create_namespace(snat_ns_name)
+ # connect snat_ports to br_int from SNAT namespace
+ for port in snat_ports:
+ # create interface_name
+ self._set_subnet_info(port)
+ interface_name = self.get_snat_int_device_name(port['id'])
+ self._internal_network_added(snat_ns_name, port['network_id'],
+ port['id'], port['ip_cidr'],
+ port['mac_address'], interface_name,
+ SNAT_INT_DEV_PREFIX)
+ self._external_gateway_added(ri, ex_gw_port, gw_interface_name,
+ internal_cidrs, snat_ns_name,
+ preserve_ips=[])
+ ri.snat_iptables_manager = (
+ iptables_manager.IptablesManager(
+ root_helper=self.root_helper, namespace=snat_ns_name
+ )
+ )
+
def external_gateway_added(self, ri, ex_gw_port,
interface_name, internal_cidrs):
-
- 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=ri.ns_name,
- prefix=EXTERNAL_DEV_PREFIX)
+ if ri.router['distributed']:
+ ip_wrapr = ip_lib.IPWrapper(self.root_helper, namespace=ri.ns_name)
+ ip_wrapr.netns.execute(['sysctl', '-w',
+ 'net.ipv4.conf.all.send_redirects=0'])
+ snat_ports = self.get_snat_interfaces(ri)
+ for p in ri.internal_ports:
+ gateway = self._map_internal_interfaces(ri, p, snat_ports)
+ id_name = self.get_internal_device_name(p['id'])
+ if gateway:
+ self._snat_redirect_add(ri, gateway['fixed_ips'][0]
+ ['ip_address'], p, id_name)
+
+ if self.conf.agent_mode == 'dvr_snat' and (
+ ri.router['gw_port_host'] == self.host):
+ if snat_ports:
+ self._create_dvr_gateway(ri, ex_gw_port,
+ interface_name,
+ internal_cidrs, snat_ports)
+ for port in snat_ports:
+ for ip in port['fixed_ips']:
+ self._update_arp_entry(ri, ip['ip_address'],
+ port['mac_address'],
+ ip['subnet_id'], 'add')
+ return
# Compute a list of addresses this router is supposed to have.
# This avoids unnecessarily removing those addresses and
# causing a momentarily network outage.
- floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
+ floating_ips = self.get_floating_ips(ri)
preserve_ips = [ip['floating_ip_address'] + FLOATING_IP_CIDR_SUFFIX
for ip in floating_ips]
+ self._external_gateway_added(ri, ex_gw_port, interface_name,
+ internal_cidrs, ri.ns_name,
+ preserve_ips)
+
+ def _external_gateway_added(self, ri, ex_gw_port, interface_name,
+ internal_cidrs, ns_name, preserve_ips):
+ 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=EXTERNAL_DEV_PREFIX)
+
self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
- namespace=ri.ns_name,
+ namespace=ns_name,
gateway=ex_gw_port['subnet'].get('gateway_ip'),
extra_subnets=ex_gw_port.get('extra_subnets', []),
preserve_ips=preserve_ips)
ip_address = ex_gw_port['ip_cidr'].split('/')[0]
- self._send_gratuitous_arp_packet(ri, interface_name, ip_address)
+ self._send_gratuitous_arp_packet(ns_name,
+ interface_name, ip_address)
+
+ 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]
+ self._send_gratuitous_arp_packet(ns_name, interface_name, ip_address)
+
+ 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 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 external_gateway_removed(self, ri, ex_gw_port,
interface_name, internal_cidrs):
+ if ri.router['distributed']:
+ for p in ri.internal_ports:
+ internal_interface = self.get_internal_device_name(p['id'])
+ self._snat_redirect_remove(ri, p, internal_interface)
+
+ if self.conf.agent_mode == 'dvr_snat' and (
+ ex_gw_port['binding:host_id'] == self.host):
+ ns_name = self.get_snat_ns_name(ri.router['id'])
+ else:
+ # not hosting agent - no work to do
+ LOG.debug('DVR: CSNAT not hosted: %s', ex_gw_port)
+ return
+ else:
+ ns_name = ri.ns_name
self.driver.unplug(interface_name,
bridge=self.conf.external_network_bridge,
- namespace=ri.ns_name,
+ namespace=ns_name,
prefix=EXTERNAL_DEV_PREFIX)
+ if ri.router['distributed']:
+ self._destroy_snat_namespace(ns_name)
def metadata_filter_rules(self):
rules = []
rules.extend(self.internal_network_nat_rules(ex_gw_ip, cidr))
return rules
- def internal_network_added(self, ri, network_id, port_id,
- internal_cidr, mac_address):
- interface_name = self.get_internal_device_name(port_id)
+ def _snat_redirect_add(self, ri, gateway, sn_port, sn_int):
+ """Adds rules and routes for SNAT redirection."""
+ try:
+ snat_idx = netaddr.IPNetwork(sn_port['ip_cidr']).value
+ ns_ipr = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
+ ns_ipd = ip_lib.IPDevice(sn_int, self.root_helper,
+ namespace=ri.ns_name)
+ ns_ipd.route.add_gateway(gateway, table=snat_idx)
+ ns_ipr.add_rule_from(sn_port['ip_cidr'], snat_idx, snat_idx)
+ ns_ipr.netns.execute(['sysctl', '-w', 'net.ipv4.conf.%s.'
+ 'send_redirects=0' % sn_int])
+ except Exception:
+ LOG.exception(_('DVR: error adding redirection logic'))
+
+ def _snat_redirect_remove(self, ri, sn_port, sn_int):
+ """Removes rules and routes for SNAT redirection."""
+ try:
+ snat_idx = netaddr.IPNetwork(sn_port['ip_cidr']).value
+ ns_ipr = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
+ ns_ipd = ip_lib.IPDevice(sn_int, self.root_helper,
+ namespace=ri.ns_name)
+ ns_ipd.route.delete_gateway(table=snat_idx)
+ ns_ipr.delete_rule_priority(snat_idx)
+ except Exception:
+ LOG.exception(_('DVR: removed snat failed'))
+
+ def _internal_network_added(self, ns_name, network_id, port_id,
+ internal_cidr, mac_address,
+ interface_name, prefix):
if not ip_lib.device_exists(interface_name,
root_helper=self.root_helper,
- namespace=ri.ns_name):
+ namespace=ns_name):
self.driver.plug(network_id, port_id, interface_name, mac_address,
- namespace=ri.ns_name,
- prefix=INTERNAL_DEV_PREFIX)
+ namespace=ns_name,
+ prefix=prefix)
self.driver.init_l3(interface_name, [internal_cidr],
- namespace=ri.ns_name)
+ namespace=ns_name)
ip_address = internal_cidr.split('/')[0]
- self._send_gratuitous_arp_packet(ri, interface_name, ip_address)
+ self._send_gratuitous_arp_packet(ns_name, interface_name, ip_address)
+
+ def internal_network_added(self, ri, port):
+ network_id = port['network_id']
+ port_id = port['id']
+ internal_cidr = port['ip_cidr']
+ mac_address = port['mac_address']
+
+ interface_name = self.get_internal_device_name(port_id)
+
+ self._internal_network_added(ri.ns_name, network_id, port_id,
+ internal_cidr, mac_address,
+ interface_name, INTERNAL_DEV_PREFIX)
- def internal_network_removed(self, ri, port_id, internal_cidr):
+ ex_gw_port = self._get_ex_gw_port(ri)
+ if ri.router['distributed'] and ex_gw_port:
+ snat_ports = self.get_snat_interfaces(ri)
+ snat_ip = self._map_internal_interfaces(ri, port, snat_ports)
+ if snat_ip:
+ self._snat_redirect_add(ri, snat_ip['fixed_ips'][0]
+ ['ip_address'], port, interface_name)
+ if self.conf.agent_mode == 'dvr_snat' and (
+ ri.router['gw_port_host'] == self.host):
+ ns_name = self.get_snat_ns_name(ri.router['id'])
+ for port in snat_ports:
+ self._set_subnet_info(port)
+ interface_name = self.get_snat_int_device_name(port['id'])
+ self._internal_network_added(ns_name, port['network_id'],
+ port['id'], internal_cidr,
+ port['mac_address'],
+ interface_name,
+ SNAT_INT_DEV_PREFIX)
+
+ def internal_network_removed(self, ri, port):
+ port_id = port['id']
interface_name = self.get_internal_device_name(port_id)
+ if ri.router['distributed'] and ri.ex_gw_port:
+ # DVR handling code for SNAT
+ self._snat_redirect_remove(ri, port, interface_name)
+ if self.conf.agent_mode == 'dvr_snat' and (
+ ri.ex_gw_port['binding:host_id'] == self.host):
+ snat_port = self._map_internal_interfaces(ri, port,
+ ri.snat_ports)
+ if snat_port:
+ snat_interface = (
+ self.get_snat_int_device_name(snat_port['id'])
+ )
+ ns_name = self.get_snat_ns_name(ri.router['id'])
+ prefix = SNAT_INT_DEV_PREFIX
+ if ip_lib.device_exists(snat_interface,
+ root_helper=self.root_helper,
+ namespace=ns_name):
+ self.driver.unplug(snat_interface, namespace=ns_name,
+ prefix=prefix)
+
if ip_lib.device_exists(interface_name,
root_helper=self.root_helper,
namespace=ri.ns_name):
(internal_cidr, ex_gw_ip))]
return rules
+ 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(_('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)
+ 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_2_fip is None:
+ ri.rtr_2_fip = FIP_LL_PREFIX + str(self.local_ips.pop())
+ if ri.fip_2_rtr is None:
+ ri.fip_2_rtr = FIP_LL_PREFIX + str(self.local_ips.pop())
+ ip_wrapper = ip_lib.IPWrapper(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(ri.rtr_2_fip + '/31',
+ rtr_2_fip_name, ri.ns_name)
+ self.internal_ns_interface_added(ri.fip_2_rtr + '/31',
+ 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(ri.fip_2_rtr, table=FIP_RT_TBL)
+ #setup the NAT rules and chains
+ self._handle_router_fip_nat_rules(ri, rtr_2_fip_name, 'add_rules')
+
+ def floating_ip_added_dist(self, ri, fip):
+ """Add floating IP to FIP namespace."""
+ floating_ip = fip['floating_ip_address']
+ fixed_ip = fip['fixed_ip_address']
+ rule_pr = self.fip_priorities.pop()
+ ri.floating_ips_dict[floating_ip] = rule_pr
+ fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id)
+ ip_rule = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
+ ip_rule.add_rule_from(fixed_ip, FIP_RT_TBL, rule_pr)
+
+ #Add routing rule in fip namespace
+ fip_cidr = str(floating_ip) + FLOATING_IP_CIDR_SUFFIX
+ fip_ns_name = self.get_fip_ns_name(str(fip['floating_network_id']))
+ device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper,
+ namespace=fip_ns_name)
+ device.route.add_route(fip_cidr, ri.rtr_2_fip)
+ interface_name = (
+ self.get_fip_ext_device_name(self.agent_gateway_port['id']))
+ self._send_gratuitous_arp_packet(fip_ns_name,
+ interface_name, floating_ip,
+ distributed=True)
+ # update internal structures
+ self.agent_fip_count = self.agent_fip_count + 1
+ ri.dist_fip_count = ri.dist_fip_count + 1
+
+ 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)
+ fip_ns_name = self.get_fip_ns_name(str(self._fetch_external_net_id()))
+ ip_rule_rtr = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name)
+ if floating_ip in ri.floating_ips_dict:
+ rule_pr = ri.floating_ips_dict[floating_ip]
+ #TODO(rajeev): Handle else case - exception/log?
+ else:
+ rule_pr = None
+
+ ip_rule_rtr.delete_rule_priority(rule_pr)
+ self.fip_priorities.add(rule_pr)
+ device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper,
+ namespace=fip_ns_name)
+
+ device.route.delete_route(fip_cidr, ri.rtr_2_fip)
+ # check if this is the last FIP for this router
+ ri.dist_fip_count = ri.dist_fip_count - 1
+ if ri.dist_fip_count == 0:
+ #remove default route entry
+ device = ip_lib.IPDevice(rtr_2_fip_name, self.root_helper,
+ namespace=ri.ns_name)
+ device.route.delete_gateway(ri.fip_2_rtr, table=FIP_RT_TBL)
+ self.local_ips.add(ri.rtr_2_fip.rsplit('.', 1)[1])
+ ri.rtr_2_fip = None
+ self.local_ips.add(ri.fip_2_rtr.rsplit('.', 1)[1])
+ ri.fip_2_rtr = None
+ # TODO(mrsmith): remove interface
+ # clean up fip-namespace if this is the last FIP
+ self.agent_fip_count = self.agent_fip_count - 1
+ if self.agent_fip_count == 0:
+ self._destroy_fip_namespace(fip_ns_name)
+
def floating_forward_rules(self, floating_ip, fixed_ip):
return [('PREROUTING', '-d %s -j DNAT --to %s' %
(floating_ip, fixed_ip)),
update = RouterUpdate(router_id, PRIORITY_RPC, action=DELETE_ROUTER)
self._queue.add(update)
+ def _update_arp_entry(self, ri, ip, mac, subnet_id, operation):
+ """Add or delete arp entry into router namespace."""
+ port = self.get_internal_port(ri, subnet_id)
+ if 'id' in port:
+ ip_cidr = str(ip) + '/32'
+ try:
+ # TODO(mrsmith): optimize the calls below for bulk calls
+ net = netaddr.IPNetwork(ip_cidr)
+ interface_name = self.get_internal_device_name(port['id'])
+ device = ip_lib.IPDevice(interface_name, self.root_helper,
+ namespace=ri.ns_name)
+ if operation == 'add':
+ device.neigh.add(net.version, ip, mac)
+ elif operation == 'delete':
+ device.neigh.delete(net.version, ip, mac)
+ except Exception:
+ LOG.exception(_("DVR: Failed updating arp entry"))
+ self.fullsync = True
+
+ def add_arp_entry(self, context, payload):
+ """Add arp entry into router namespace. Called from RPC."""
+ arp_table = payload['arp_table']
+ router_id = payload['router_id']
+ ip = arp_table['ip_address']
+ mac = arp_table['mac_address']
+ subnet_id = arp_table['subnet_id']
+ ri = self.router_info.get(router_id)
+ self._update_arp_entry(ri, ip, mac, subnet_id, 'add')
+
+ def del_arp_entry(self, context, payload):
+ """Delete arp entry from router namespace. Called from RPC."""
+ arp_table = payload['arp_table']
+ router_id = payload['router_id']
+ ip = arp_table['ip_address']
+ mac = arp_table['mac_address']
+ subnet_id = arp_table['subnet_id']
+ ri = self.router_info.get(router_id)
+ if ri:
+ self._update_arp_entry(ri, ip, mac, subnet_id, 'delete')
+
def routers_updated(self, context, routers):
"""Deal with routers modification and creation RPC message."""
LOG.debug(_('Got routers updated notification :%s'), routers)
'host': host,
'topic': topics.L3_AGENT,
'configurations': {
+ 'agent_mode': self.conf.agent_mode,
'use_namespaces': self.conf.use_namespaces,
'router_id': self.conf.router_id,
'handle_internal_only_routers':
HOSTNAME = 'myhost'
FAKE_ID = _uuid()
FAKE_ID_2 = _uuid()
+FIP_PRI = 32768
class TestExclusiveRouterProcessor(base.BaseTestCase):
self.mock_ip = mock.MagicMock()
ip_cls.return_value = self.mock_ip
+ ip_rule = mock.patch('neutron.agent.linux.ip_lib.IpRule').start()
+ self.mock_rule = mock.MagicMock()
+ ip_rule.return_value = self.mock_rule
+
+ ip_dev = mock.patch('neutron.agent.linux.ip_lib.IPDevice').start()
+ self.mock_ip_dev = mock.MagicMock()
+ ip_dev.return_value = self.mock_ip_dev
+
self.l3pluginApi_cls_p = mock.patch(
'neutron.agent.l3_agent.L3PluginApi')
l3pluginApi_cls = self.l3pluginApi_cls_p.start()
'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall')
self.looping_call_p.start()
+ self.subnet_id_list = []
+
def test__sync_routers_task_raise_exception(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
self.plugin_api.get_routers.side_effect = Exception()
def test_router_info_create(self):
id = _uuid()
ri = l3_agent.RouterInfo(id, self.conf.root_helper,
- self.conf.use_namespaces, None)
+ self.conf.use_namespaces, {})
self.assertTrue(ri.ns_name.endswith(id))
port_id = _uuid()
router_id = _uuid()
network_id = _uuid()
+ router = self._prepare_router_data(num_internal_ports=2)
+ router_id = router['id']
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
- self.conf.use_namespaces, None)
+ self.conf.use_namespaces, router=router)
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
cidr = '99.0.1.9/24'
mac = 'ca:fe:de:ad:be:ef'
+ port = {'network_id': network_id,
+ 'id': port_id, 'ip_cidr': cidr,
+ 'mac_address': mac}
interface_name = agent.get_internal_device_name(port_id)
if action == 'add':
self.device_exists.return_value = False
- agent.internal_network_added(ri, network_id,
- port_id, cidr, mac)
+ agent.internal_network_added(ri, port)
self.assertEqual(self.mock_driver.plug.call_count, 1)
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
- self.send_arp.assert_called_once_with(ri, interface_name,
+ self.send_arp.assert_called_once_with(ri.ns_name, interface_name,
'99.0.1.9')
elif action == 'remove':
self.device_exists.return_value = True
- agent.internal_network_removed(ri, port_id, cidr)
+ agent.internal_network_removed(ri, port)
self.assertEqual(self.mock_driver.unplug.call_count, 1)
else:
raise Exception("Invalid action %s" % action)
self._test_internal_network_action('remove')
def _test_external_gateway_action(self, action):
- router_id = _uuid()
- ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
- self.conf.use_namespaces, None)
+ router = self._prepare_router_data(num_internal_ports=2)
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
internal_cidrs = ['100.0.1.0/24', '200.74.0.0/16']
ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
if action == 'add':
self.device_exists.return_value = False
- ri.router = mock.Mock()
- ri.router.get.return_value = [{'floating_ip_address':
- '192.168.1.34'}]
+ fake_fip = {'floatingips': [{'id': _uuid(),
+ 'floating_ip_address': '192.168.1.34',
+ 'fixed_ip_address': '192.168.0.1',
+ 'port_id': _uuid()}]}
+ router[l3_constants.FLOATINGIP_KEY] = fake_fip['floatingips']
agent.external_gateway_added(ri, ex_gw_port,
interface_name, internal_cidrs)
self.assertEqual(self.mock_driver.plug.call_count, 1)
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
- self.send_arp.assert_called_once_with(ri, interface_name,
+ self.send_arp.assert_called_once_with(ri.ns_name, interface_name,
'20.0.0.30')
kwargs = {'preserve_ips': ['192.168.1.34/32'],
- 'namespace': 'qrouter-' + router_id,
+ 'namespace': 'qrouter-' + router['id'],
'gateway': '20.0.0.1',
'extra_subnets': [{'cidr': '172.16.0.0/24'}]}
self.mock_driver.init_l3.assert_called_with(interface_name,
router_id = _uuid()
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
- self.conf.use_namespaces, None)
+ self.conf.use_namespaces, {})
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
floating_ip = '20.0.0.101'
interface_name = agent.get_external_device_name(router_id)
router_id = _uuid()
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
self.conf.use_namespaces,
- None)
+ {})
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
fake_route1 = {'destination': '135.207.0.0/16',
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
self.conf.use_namespaces,
- None)
+ {})
ri.router = {}
fake_old_routes = []
self.assertIn(r.rule, expected_rules)
@staticmethod
- def _router_append_interface(router, count=1, ip_version=4,
+ def _router_append_interface(router, subnet_id_list=[], count=1,
+ ip_version=4,
ra_mode=None, addr_mode=None):
if ip_version == 4:
ip_pool = '35.4.%i.4'
for p in interfaces])
for i in range(current, current + count):
+ if subnet_id_list:
+ subnet_id_list.append(_uuid())
+ subnet_id = subnet_id_list[i]
+ else:
+ subnet_id = _uuid()
interfaces.append(
{'id': _uuid(),
'network_id': _uuid(),
'admin_state_up': True,
'fixed_ips': [{'ip_address': ip_pool % i,
- 'subnet_id': _uuid()}],
+ 'subnet_id': subnet_id}],
'mac_address': 'ca:fe:de:ad:be:ef',
'subnet': {'cidr': cidr_pool % i,
'gateway_ip': gw_pool % i,
'subnet_id': _uuid()}],
'subnet': {'cidr': cidr,
'gateway_ip': gateway_ip}}
+ int_ports = []
+ self.subnet_id_list = []
+ for i in range(num_internal_ports):
+ self.subnet_id_list.append(_uuid())
+ subnet_id = self.subnet_id_list[i]
+ int_ports.append({'id': _uuid(),
+ 'network_id': _uuid(),
+ 'admin_state_up': True,
+ 'fixed_ips': [{'ip_address': '35.4.%s.4' % i,
+ 'subnet_id': subnet_id}],
+ 'mac_address': 'ca:fe:de:ad:be:ef',
+ 'subnet': {'cidr': '35.4.%s.0/24' % i,
+ 'gateway_ip': '35.4.%s.1' % i}})
router = {
'id': router_id,
+ 'distributed': False,
l3_constants.INTERFACE_KEY: [],
'routes': [],
'gw_port': ex_gw_port}
- self._router_append_interface(router, count=num_internal_ports,
+ self._router_append_interface(router, self.subnet_id_list,
+ count=num_internal_ports,
ip_version=ip_version)
if enable_snat is not None:
router['enable_snat'] = enable_snat
return router
- def test_process_router(self):
+ def test__map_internal_interfaces(self):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = self._prepare_router_data(num_internal_ports=4)
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+ test_port = {'mac_address': '00:12:23:34:45:56',
+ 'fixed_ips': [{'subnet_id': self.subnet_id_list[0],
+ 'ip_address': '101.12.13.14'}]}
+ internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
+ # test valid case
+ res_port = agent._map_internal_interfaces(ri,
+ internal_ports[0],
+ [test_port])
+ self.assertEqual(test_port, res_port)
+ # test invalid case
+ test_port['fixed_ips'][0]['subnet_id'] = 1234
+ res_ip = agent._map_internal_interfaces(ri,
+ internal_ports[0],
+ [test_port])
+ self.assertNotEqual(test_port, res_ip)
+ self.assertIsNone(res_ip)
+
+ def test_get_internal_port(self):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = self._prepare_router_data(num_internal_ports=4)
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+
+ # Test Basic cases
+ port = agent.get_internal_port(ri, self.subnet_id_list[0])
+ fips = port.get('fixed_ips', [])
+ subnet_id = fips[0]['subnet_id']
+ self.assertEqual(self.subnet_id_list[0], subnet_id)
+ port = agent.get_internal_port(ri, self.subnet_id_list[1])
+ fips = port.get('fixed_ips', [])
+ subnet_id = fips[0]['subnet_id']
+ self.assertEqual(self.subnet_id_list[1], subnet_id)
+ port = agent.get_internal_port(ri, self.subnet_id_list[3])
+ fips = port.get('fixed_ips', [])
+ subnet_id = fips[0]['subnet_id']
+ self.assertEqual(self.subnet_id_list[3], subnet_id)
+
+ # Test miss cases
+ no_port = agent.get_internal_port(ri, FAKE_ID)
+ self.assertIsNone(no_port)
+ port = agent.get_internal_port(ri, self.subnet_id_list[0])
+ fips = port.get('fixed_ips', [])
+ subnet_id = fips[0]['subnet_id']
+ self.assertNotEqual(self.subnet_id_list[3], subnet_id)
+
+ def test__set_subnet_arp_info(self):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = self._prepare_router_data(num_internal_ports=2)
+ router['distributed'] = True
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+ ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
+ test_ports = [{'mac_address': '00:11:22:33:44:55',
+ 'device_owner': 'network:dhcp',
+ 'subnet_id': self.subnet_id_list[0],
+ 'fixed_ips': [{'ip_address': '1.2.3.4'}]}]
+
+ self.plugin_api.get_ports_by_subnet.return_value = test_ports
+
+ # Test basic case
+ ports[0]['subnet']['id'] = self.subnet_id_list[0]
+ agent._set_subnet_arp_info(ri, ports[0])
+ self.mock_ip_dev.neigh.add.assert_called_once_with(
+ 4, '1.2.3.4', '00:11:22:33:44:55')
+
+ # Test negative case
+ router['distributed'] = False
+ agent._set_subnet_arp_info(ri, ports[0])
+ self.mock_ip_dev.neigh.add.never_called()
+
+ def test_add_arp_entry(self):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = self._prepare_router_data(num_internal_ports=2)
+ arp_table = {'ip_address': '1.7.23.11',
+ 'mac_address': '00:11:22:33:44:55',
+ 'subnet_id': self.subnet_id_list[0]}
+
+ payload = {'arp_table': arp_table, 'router_id': router['id']}
+ agent._router_added(router['id'], router)
+ agent.add_arp_entry(None, payload)
+ agent.router_deleted(None, router['id'])
+ self.mock_ip_dev.neigh.add.assert_called_once_with(
+ 4, '1.7.23.11', '00:11:22:33:44:55')
+
+ def test_del_arp_entry(self):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = self._prepare_router_data(num_internal_ports=2)
+ arp_table = {'ip_address': '1.5.25.15',
+ 'mac_address': '00:44:33:22:11:55',
+ 'subnet_id': self.subnet_id_list[0]}
+
+ payload = {'arp_table': arp_table, 'router_id': router['id']}
+ agent._router_added(router['id'], router)
+ # first add the entry
+ agent.add_arp_entry(None, payload)
+ # now delete it
+ agent.del_arp_entry(None, payload)
+ self.mock_ip_dev.neigh.delete.assert_called_once_with(
+ 4, '1.5.25.15', '00:44:33:22:11:55')
+ agent.router_deleted(None, router['id'])
+
+ def test_process_cent_router(self):
+ router = self._prepare_router_data()
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+ self._test_process_router(ri)
+
+ def test_process_dist_router(self):
+ router = self._prepare_router_data()
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+ ri.router['distributed'] = True
+ ri.router['_snat_router_interfaces'] = [{
+ 'fixed_ips': [{'subnet_id': self.subnet_id_list[0],
+ 'ip_address': '1.2.3.4'}]}]
+ ri.router['gw_port_host'] = None
+ self._test_process_router(ri)
+
+ def _test_process_router(self, ri):
+ router = ri.router
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
fake_fip_id = 'fake_fip_id'
agent.process_router_floating_ip_addresses = mock.Mock()
agent.process_router_floating_ip_addresses.return_value = {
fake_fip_id: 'ACTIVE'}
agent.external_gateway_added = mock.Mock()
- router = self._prepare_router_data()
fake_floatingips1 = {'floatingips': [
{'id': fake_fip_id,
'floating_ip_address': '8.8.8.8',
'fixed_ip_address': '7.7.7.7',
'port_id': _uuid()}]}
- ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
- self.conf.use_namespaces, router=router)
agent.process_router(ri)
ex_gw_port = agent._get_ex_gw_port(ri)
agent.process_router_floating_ip_addresses.assert_called_with(
self.assertFalse(agent.process_router_floating_ip_nat_rules.called)
@mock.patch('neutron.agent.linux.ip_lib.IPDevice')
- def test_process_router_floating_ip_addresses_add(self, IPDevice):
- fip_id = _uuid()
- fip = {
- 'id': fip_id, 'port_id': _uuid(),
- 'floating_ip_address': '15.1.2.3',
- 'fixed_ip_address': '192.168.0.1'
- }
-
+ def _test_process_router_floating_ip_addresses_add(self, ri,
+ agent, IPDevice):
+ floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
+ fip_id = floating_ips[0]['id']
IPDevice.return_value = device = mock.Mock()
device.addr.list.return_value = []
-
- ri = mock.MagicMock()
- ri.router.get.return_value = [fip]
-
- agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
fip_statuses = agent.process_router_floating_ip_addresses(
ri, {'id': _uuid()})
ri = mock.MagicMock()
ri.router.get.return_value = [fip]
+ ri.router['distributed'].__nonzero__ = lambda self: False
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
for chain, rule in rules:
nat.add_rule.assert_any_call(chain, rule, tag='floating_ip')
+ def test_process_router_cent_floating_ip_add(self):
+ fake_floatingips = {'floatingips': [
+ {'id': _uuid(),
+ 'floating_ip_address': '15.1.2.3',
+ 'fixed_ip_address': '192.168.0.1',
+ 'port_id': _uuid()}]}
+
+ router = self._prepare_router_data(enable_snat=True)
+ router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips']
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+ ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ self._test_process_router_floating_ip_addresses_add(ri, agent)
+
+ def test_process_router_dist_floating_ip_add(self):
+ fake_floatingips = {'floatingips': [
+ {'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()}]}
+
+ router = self._prepare_router_data(enable_snat=True)
+ router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips']
+ router['distributed'] = True
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+ ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ agent.host = HOSTNAME
+ 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'}
+ )
+ self._test_process_router_floating_ip_addresses_add(ri, agent)
+
+ # TODO(mrsmith): refactor for DVR cases
@mock.patch('neutron.agent.linux.ip_lib.IPDevice')
def test_process_router_floating_ip_addresses_remove(self, IPDevice):
IPDevice.return_value = device = mock.Mock()
ri = mock.MagicMock()
ri.router.get.return_value = []
+ ri.router['distributed'].__nonzero__ = lambda self: False
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
IPDevice.return_value = device = mock.Mock()
device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}]
ri = mock.MagicMock()
+ ri.router['distributed'].__nonzero__ = lambda self: False
ri.router.get.return_value = [fip]
}
ri = mock.MagicMock()
ri.router.get.return_value = [fip]
+ ri.router['distributed'].__nonzero__ = lambda self: False
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
ri = mock.MagicMock()
port = {'fixed_ips': [{'ip_address': '192.168.1.4'}]}
+ ri.router = {'distributed': False}
agent._handle_router_snat_rules(ri, port, [], "iface", "add_rules")
def test_handle_router_snat_rules_add_rules(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
ri = l3_agent.RouterInfo(_uuid(), self.conf.root_helper,
- self.conf.use_namespaces, None)
+ self.conf.use_namespaces, {})
ex_gw_port = {'fixed_ips': [{'ip_address': '192.168.1.4'}]}
internal_cidrs = ['10.0.0.0/24']
+ ri.router = {'distributed': False}
agent._handle_router_snat_rules(ri, ex_gw_port, internal_cidrs,
"iface", "add_rules")
self.assertFalse(external_gateway_removed.called)
self.assertFalse(internal_network_removed.called)
internal_network_added.assert_called_once_with(
- ri,
- internal_port['network_id'],
- internal_port['id'],
- internal_port['ip_cidr'],
- internal_port['mac_address'])
+ ri, internal_port)
self.assertEqual(self.mock_driver.unplug.call_count,
len(stale_devnames))
calls = [mock.call(stale_devname,
'enable_snat': True,
'routes': [],
'gw_port': ex_gw_port}
+ router['distributed'] = False
agent._router_added(router['id'], router)
agent.router_deleted(None, router['id'])
agent._process_router_delete()
self.assertFalse(list(agent.removed_routers))
+ def test_destroy_fip_namespace(self):
+ class FakeDev(object):
+ def __init__(self, name):
+ self.name = name
+
+ namespaces = ['qrouter-foo', 'qrouter-bar']
+
+ self.mock_ip.get_namespaces.return_value = namespaces
+ self.mock_ip.get_devices.return_value = [FakeDev('fr-aaaa'),
+ FakeDev('fg-aaaa')]
+
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+
+ agent._destroy_fip_namespace(namespaces[0])
+ # TODO(mrsmith): update for fr interface
+ self.assertEqual(self.mock_driver.unplug.call_count, 1)
+ self.mock_driver.unplug.assert_called_with('fg-aaaa', bridge='br-ex',
+ prefix='fg-',
+ namespace='qrouter-foo')
+
def test_destroy_router_namespace_skips_ns_removal(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
agent._destroy_router_namespace("fakens")
router_id = _uuid()
router = {'id': _uuid(),
'external_gateway_info': {},
- 'routes': []}
+ 'routes': [],
+ 'distributed': False}
with mock.patch.object(
agent, '_destroy_metadata_proxy') as destroy_proxy:
with mock.patch.object(
self.conf.set_override('router_id', None)
stale_namespaces = [l3_agent.NS_PREFIX + 'cccc',
l3_agent.NS_PREFIX + 'eeeee']
- router_list = [{'id': 'foo'}, {'id': 'aaaa'}]
+ router_list = [{'id': 'foo', 'distributed': False},
+ {'id': 'aaaa', 'distributed': False}]
other_namespaces = ['qdhcp-aabbcc', 'unknown']
self._cleanup_namespace_test(stale_namespaces,
stale_namespaces = [l3_agent.NS_PREFIX + 'cccc',
l3_agent.NS_PREFIX + 'eeeee',
l3_agent.NS_PREFIX + self.conf.router_id]
- router_list = [{'id': 'foo'}, {'id': 'aaaa'}]
+ router_list = [{'id': 'foo', 'distributed': False},
+ {'id': 'aaaa', 'distributed': False}]
other_namespaces = ['qdhcp-aabbcc', 'unknown']
self._cleanup_namespace_test(stale_namespaces,
router_list,
other_namespaces)
+ def test_create_dvr_gateway(self):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = self._prepare_router_data()
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+
+ port_id = _uuid()
+ dvr_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': _uuid(),
+ 'mac_address': 'ca:fe:de:ad:be:ef',
+ 'ip_cidr': '20.0.0.30/24'}
+
+ snat_ports = [{'subnet': {'cidr': '152.2.0.0/16',
+ 'gateway_ip': '152.2.0.1',
+ 'id': _uuid()},
+ 'network_id': _uuid(),
+ 'device_owner': 'network:router_centralized_snat',
+ 'ip_cidr': '152.2.0.13/16',
+ 'mac_address': 'fa:16:3e:80:8d:80',
+ 'fixed_ips': [{'subnet_id': _uuid(),
+ 'ip_address': '152.2.0.13'}],
+ 'id': _uuid(), 'device_id': _uuid()},
+ {'subnet': {'cidr': '152.10.0.0/16',
+ 'gateway_ip': '152.10.0.1',
+ 'id': _uuid()},
+ 'network_id': _uuid(),
+ 'device_owner': 'network:router_centralized_snat',
+ 'ip_cidr': '152.10.0.13/16',
+ 'mac_address': 'fa:16:3e:80:8d:80',
+ 'fixed_ips': [{'subnet_id': _uuid(),
+ 'ip_address': '152.10.0.13'}],
+ 'id': _uuid(), 'device_id': _uuid()}]
+
+ interface_name = agent.get_snat_int_device_name(port_id)
+ internal_cidrs = None
+ self.device_exists.return_value = False
+
+ agent._create_dvr_gateway(ri, dvr_gw_port, interface_name,
+ internal_cidrs, snat_ports)
+
+ # check 2 internal ports are plugged
+ # check 1 ext-gw-port is plugged
+ 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')
+ 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 = self._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 = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+
+ 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']))
+
+ 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(
+ ri.fip_2_rtr, table=16)
+
+ # TODO(mrsmith): test _create_agent_gateway_port
+
+ def test_floating_ip_added_dist(self):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = self._prepare_router_data()
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+ 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(),
+ 'mac_address': 'ca:fe:de:ad:be:ef',
+ 'ip_cidr': '20.0.0.30/24'}
+
+ 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()}
+ agent.agent_gateway_port = agent_gw_port
+ agent.floating_ip_added_dist(ri, fip)
+ self.mock_rule.add_rule_from.assert_called_with('192.168.0.1',
+ 16, FIP_PRI)
+ # TODO(mrsmith): add more asserts
+
+ def test_floating_ip_removed_dist(self):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = self._prepare_router_data()
+ 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(),
+ 'mac_address': 'ca:fe:de:ad:be:ef',
+ 'ip_cidr': '20.0.0.30/24'}
+
+ fip_cidr = '11.22.33.44/24'
+
+ ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
+ self.conf.use_namespaces, router=router)
+ ri.dist_fip_count = 2
+ ri.floating_ips_dict['11.22.33.44'] = FIP_PRI
+ agent.agent_gateway_port = agent_gw_port
+ agent.floating_ip_removed_dist(ri, fip_cidr)
+ self.mock_rule.delete_rule_priority.assert_called_with(FIP_PRI)
+ self.mock_ip_dev.route.delete_route.assert_called_with(fip_cidr,
+ ri.rtr_2_fip)
+ # TODO(mrsmith): test ri.dist_fip_count == 0
+ # TODO(mrsmith): test agent_fip_count == 0 case
+
class TestL3AgentEventHandler(base.BaseTestCase):