Merge "Modify L3 Agent for Distributed Routers"
This commit is contained in:
commit
84e4595f17
@ -77,3 +77,14 @@
|
||||
# Timeout for ovs-vsctl commands.
|
||||
# If the timeout expires, ovs commands will fail with ALARMCLOCK error.
|
||||
# ovs_vsctl_timeout = 10
|
||||
|
||||
# The working mode for the agent. Allowed values 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).
|
||||
# agent_mode = legacy
|
||||
|
@ -28,7 +28,6 @@ from neutron.agent.linux import external_process
|
||||
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
|
||||
@ -53,6 +52,18 @@ LOG = logging.getLogger(__name__)
|
||||
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
|
||||
@ -67,6 +78,10 @@ class L3PluginApi(n_rpc.RpcProxy):
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
@ -105,6 +120,22 @@ class L3PluginApi(n_rpc.RpcProxy):
|
||||
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):
|
||||
|
||||
@ -114,7 +145,9 @@ 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
|
||||
@ -125,6 +158,12 @@ class RouterInfo(object):
|
||||
#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):
|
||||
@ -301,10 +340,27 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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.")),
|
||||
@ -366,6 +422,12 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
|
||||
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)
|
||||
|
||||
@ -425,41 +487,89 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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."""
|
||||
@ -553,6 +663,24 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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:
|
||||
@ -570,9 +698,13 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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']])
|
||||
@ -586,15 +718,15 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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):
|
||||
@ -653,8 +785,10 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
# 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 = {}
|
||||
@ -684,6 +818,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
|
||||
# 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,
|
||||
@ -692,13 +827,19 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
# 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')
|
||||
|
||||
# And add them back if the action if add_rules
|
||||
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 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
|
||||
@ -709,8 +850,31 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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):
|
||||
@ -721,8 +885,9 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
# 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']
|
||||
@ -739,14 +904,33 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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
|
||||
|
||||
@ -765,10 +949,15 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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)
|
||||
|
||||
@ -777,26 +966,48 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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]
|
||||
@ -804,38 +1015,184 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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):
|
||||
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)
|
||||
|
||||
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 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 = []
|
||||
@ -863,23 +1220,100 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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']
|
||||
|
||||
def internal_network_removed(self, ri, port_id, internal_cidr):
|
||||
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)
|
||||
|
||||
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):
|
||||
@ -891,6 +1325,118 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
(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)),
|
||||
@ -905,6 +1451,46 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
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)
|
||||
@ -1115,6 +1701,7 @@ class L3NATAgentWithStateReport(L3NATAgent):
|
||||
'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':
|
||||
|
@ -92,7 +92,7 @@ class TestVPNAgent(base.BaseTestCase):
|
||||
def test_get_namespace(self):
|
||||
router_id = _uuid()
|
||||
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
|
||||
self.conf.use_namespaces, None)
|
||||
self.conf.use_namespaces, {})
|
||||
self.agent.router_info = {router_id: ri}
|
||||
namespace = self.agent.get_namespace(router_id)
|
||||
self.assertTrue(namespace.endswith(router_id))
|
||||
@ -101,7 +101,7 @@ class TestVPNAgent(base.BaseTestCase):
|
||||
def test_add_nat_rule(self):
|
||||
router_id = _uuid()
|
||||
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
|
||||
self.conf.use_namespaces, None)
|
||||
self.conf.use_namespaces, {})
|
||||
iptables = mock.Mock()
|
||||
ri.iptables_manager.ipv4['nat'] = iptables
|
||||
self.agent.router_info = {router_id: ri}
|
||||
@ -121,7 +121,7 @@ class TestVPNAgent(base.BaseTestCase):
|
||||
def test_remove_rule(self):
|
||||
router_id = _uuid()
|
||||
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
|
||||
self.conf.use_namespaces, None)
|
||||
self.conf.use_namespaces, {})
|
||||
iptables = mock.Mock()
|
||||
ri.iptables_manager.ipv4['nat'] = iptables
|
||||
self.agent.router_info = {router_id: ri}
|
||||
@ -140,7 +140,7 @@ class TestVPNAgent(base.BaseTestCase):
|
||||
def test_iptables_apply(self):
|
||||
router_id = _uuid()
|
||||
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
|
||||
self.conf.use_namespaces, None)
|
||||
self.conf.use_namespaces, {})
|
||||
iptables = mock.Mock()
|
||||
ri.iptables_manager = iptables
|
||||
self.agent.router_info = {router_id: ri}
|
||||
@ -168,12 +168,13 @@ class TestVPNAgent(base.BaseTestCase):
|
||||
'neutron.agent.linux.iptables_manager.IptablesManager').start()
|
||||
router_id = _uuid()
|
||||
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
|
||||
self.conf.use_namespaces, None)
|
||||
self.conf.use_namespaces, {})
|
||||
ri.router = {
|
||||
'id': _uuid(),
|
||||
'admin_state_up': True,
|
||||
'routes': [],
|
||||
'external_gateway_info': {}}
|
||||
'external_gateway_info': {},
|
||||
'distributed': False}
|
||||
device = mock.Mock()
|
||||
self.agent.router_info = {router_id: ri}
|
||||
self.agent.devices = [device]
|
||||
|
@ -37,6 +37,7 @@ _uuid = uuidutils.generate_uuid
|
||||
HOSTNAME = 'myhost'
|
||||
FAKE_ID = _uuid()
|
||||
FAKE_ID_2 = _uuid()
|
||||
FIP_PRI = 32768
|
||||
|
||||
|
||||
class TestExclusiveRouterProcessor(base.BaseTestCase):
|
||||
@ -165,6 +166,14 @@ class TestBasicRouterOperations(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()
|
||||
@ -175,6 +184,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
'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()
|
||||
@ -192,7 +203,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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))
|
||||
|
||||
@ -221,24 +232,28 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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)
|
||||
@ -250,9 +265,9 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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',
|
||||
@ -267,17 +282,19 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
|
||||
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,
|
||||
@ -301,7 +318,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
|
||||
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)
|
||||
@ -335,7 +352,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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',
|
||||
@ -383,7 +400,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
|
||||
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
|
||||
self.conf.use_namespaces,
|
||||
None)
|
||||
{})
|
||||
ri.router = {}
|
||||
|
||||
fake_old_routes = []
|
||||
@ -446,7 +463,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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'
|
||||
@ -465,12 +483,17 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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,
|
||||
@ -497,20 +520,159 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
'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()
|
||||
@ -518,14 +680,11 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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(
|
||||
@ -566,21 +725,13 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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()})
|
||||
@ -597,6 +748,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
|
||||
ri = mock.MagicMock()
|
||||
ri.router.get.return_value = [fip]
|
||||
ri.router['distributed'].__nonzero__ = lambda self: False
|
||||
|
||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
|
||||
@ -608,6 +760,50 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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()
|
||||
@ -615,6 +811,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
|
||||
ri = mock.MagicMock()
|
||||
ri.router.get.return_value = []
|
||||
ri.router['distributed'].__nonzero__ = lambda self: False
|
||||
|
||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
|
||||
@ -647,6 +844,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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]
|
||||
|
||||
@ -693,6 +891,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
}
|
||||
ri = mock.MagicMock()
|
||||
ri.router.get.return_value = [fip]
|
||||
ri.router['distributed'].__nonzero__ = lambda self: False
|
||||
|
||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
|
||||
@ -1035,6 +1234,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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")
|
||||
|
||||
@ -1051,9 +1251,10 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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")
|
||||
|
||||
@ -1116,11 +1317,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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,
|
||||
@ -1193,11 +1390,32 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
'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")
|
||||
@ -1216,7 +1434,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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(
|
||||
@ -1439,7 +1658,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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,
|
||||
@ -1451,13 +1671,166 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
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):
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user