[Agent Side] L3 router support ndp proxy
The agent side codes need consider three scenarios: 1. Non-dvr router. The all related rules are applied in qrouter-namespace 2. Dvr router with the local agent mode is dvr_no_external. The all related rules are applied in snat-namespace. 3. Dvr router with the local agent mode is dvr. In this scenario, The all related rules are applied in fip-namespace. Change-Id: Ie8729586d318be4a673858021a0116e09e193522 Partial-Bug: #1877301
This commit is contained in:
parent
999bb965f7
commit
9b27020a65
@ -89,6 +89,14 @@ class FipNamespace(namespaces.Namespace):
|
||||
def get_rtr_ext_device_name(self, router_id):
|
||||
return (ROUTER_2_FIP_DEV_PREFIX + router_id)[:self.driver.DEV_NAME_LEN]
|
||||
|
||||
def get_fip_2_rtr_device(self, ri):
|
||||
fip_2_rtr_name = self.get_int_device_name(ri.router_id)
|
||||
return ip_lib.IPDevice(fip_2_rtr_name, ri.fip_ns.name)
|
||||
|
||||
def get_rtr_2_fip_device(self, ri):
|
||||
rtr_2_fip_name = self.get_rtr_ext_device_name(ri.router_id)
|
||||
return ip_lib.IPDevice(rtr_2_fip_name, ri.ns_name)
|
||||
|
||||
def has_subscribers(self):
|
||||
return len(self._subscribers) != 0
|
||||
|
||||
@ -410,22 +418,19 @@ class FipNamespace(namespaces.Namespace):
|
||||
def create_rtr_2_fip_link(self, ri):
|
||||
"""Create interface between router and Floating IP namespace."""
|
||||
LOG.debug("Create FIP link interfaces for router %s", ri.router_id)
|
||||
rtr_2_fip_name = self.get_rtr_ext_device_name(ri.router_id)
|
||||
fip_2_rtr_name = self.get_int_device_name(ri.router_id)
|
||||
fip_ns_name = self.get_name()
|
||||
|
||||
# add link local IP to interface
|
||||
if ri.rtr_fip_subnet is None:
|
||||
ri.rtr_fip_subnet = self.local_subnets.allocate(ri.router_id)
|
||||
rtr_2_fip, fip_2_rtr = ri.rtr_fip_subnet.get_pair()
|
||||
rtr_2_fip_dev = ip_lib.IPDevice(rtr_2_fip_name, namespace=ri.ns_name)
|
||||
fip_2_rtr_dev = ip_lib.IPDevice(fip_2_rtr_name, namespace=fip_ns_name)
|
||||
rtr_2_fip_dev = self.get_rtr_2_fip_device(ri)
|
||||
fip_2_rtr_dev = self.get_fip_2_rtr_device(ri)
|
||||
|
||||
if not rtr_2_fip_dev.exists():
|
||||
ip_wrapper = ip_lib.IPWrapper(namespace=ri.ns_name)
|
||||
rtr_2_fip_dev, fip_2_rtr_dev = ip_wrapper.add_veth(rtr_2_fip_name,
|
||||
fip_2_rtr_name,
|
||||
fip_ns_name)
|
||||
rtr_2_fip_dev, fip_2_rtr_dev = ip_wrapper.add_veth(
|
||||
rtr_2_fip_dev.name, fip_2_rtr_dev.name, fip_ns_name)
|
||||
rtr_2_fip_dev.link.set_up()
|
||||
fip_2_rtr_dev.link.set_up()
|
||||
|
||||
@ -444,10 +449,13 @@ class FipNamespace(namespaces.Namespace):
|
||||
rtr_2_fip_dev.link.address)
|
||||
|
||||
self._add_rtr_ext_route_rule_to_route_table(ri, fip_2_rtr,
|
||||
fip_2_rtr_name)
|
||||
fip_2_rtr_dev.name)
|
||||
|
||||
# add default route for the link local interface
|
||||
rtr_2_fip_dev.route.add_gateway(str(fip_2_rtr.ip), table=FIP_RT_TBL)
|
||||
v6_gateway = common_utils.cidr_to_ip(
|
||||
ip_lib.get_ipv6_lladdr(fip_2_rtr_dev.link.address))
|
||||
rtr_2_fip_dev.route.add_gateway(v6_gateway)
|
||||
|
||||
def scan_fip_ports(self, ri):
|
||||
# scan system for any existing fip ports
|
||||
|
451
neutron/agent/l3/extensions/ndp_proxy.py
Normal file
451
neutron/agent/l3/extensions/ndp_proxy.py
Normal file
@ -0,0 +1,451 @@
|
||||
# Copyright 2021 Troila
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
|
||||
import netaddr
|
||||
from neutron_lib.agent import l3_extension
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import rpc as n_rpc
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.api.rpc.callbacks.consumer import registry
|
||||
from neutron.api.rpc.callbacks import events
|
||||
from neutron.api.rpc.callbacks import resources
|
||||
from neutron.api.rpc.handlers import resources_rpc
|
||||
from neutron.common import coordination
|
||||
from neutron.common import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
DEFAULT_NDP_PROXY_CHAIN = 'NDP'
|
||||
|
||||
|
||||
class RouterNDPProxyMapping(object):
|
||||
|
||||
def __init__(self):
|
||||
self.managed_ndp_proxies = {}
|
||||
self.router_ndp_proxy_mapping = collections.defaultdict(set)
|
||||
|
||||
@lockutils.synchronized('ndp-proxy-cache')
|
||||
def set_ndp_proxies(self, ndp_proxies):
|
||||
for ndp_proxy in ndp_proxies:
|
||||
self.router_ndp_proxy_mapping[
|
||||
ndp_proxy.router_id].add(ndp_proxy.id)
|
||||
self.managed_ndp_proxies[ndp_proxy.id] = ndp_proxy
|
||||
|
||||
@lockutils.synchronized('ndp-proxy-cache')
|
||||
def get_ndp_proxy(self, ndp_proxy_id):
|
||||
return self.managed_ndp_proxies.get(ndp_proxy_id)
|
||||
|
||||
@lockutils.synchronized('ndp-proxy-cache')
|
||||
def del_ndp_proxies(self, ndp_proxies):
|
||||
for ndp_proxy in ndp_proxies:
|
||||
if not self.managed_ndp_proxies.get(ndp_proxy.id):
|
||||
continue
|
||||
del self.managed_ndp_proxies[ndp_proxy.id]
|
||||
self.router_ndp_proxy_mapping[
|
||||
ndp_proxy.router_id].remove(ndp_proxy.id)
|
||||
if not self.router_ndp_proxy_mapping[ndp_proxy.router_id]:
|
||||
del self.router_ndp_proxy_mapping[ndp_proxy.router_id]
|
||||
|
||||
@lockutils.synchronized('ndp-proxy-cache')
|
||||
def get_ndp_proxies_by_router_id(self, router_id):
|
||||
ndp_proxies = []
|
||||
router_ndp_proxy_ids = self.router_ndp_proxy_mapping.get(router_id, [])
|
||||
for ndp_proxy_id in router_ndp_proxy_ids:
|
||||
ndp_proxies.append(self.managed_ndp_proxies.get(ndp_proxy_id))
|
||||
return ndp_proxies
|
||||
|
||||
@lockutils.synchronized('ndp-proxy-cache')
|
||||
def clear_by_router_id(self, router_id):
|
||||
router_ndp_proxy_ids = self.router_ndp_proxy_mapping.get(router_id)
|
||||
if not router_ndp_proxy_ids:
|
||||
return
|
||||
for ndp_proxy_id in router_ndp_proxy_ids:
|
||||
del self.managed_ndp_proxies[ndp_proxy_id]
|
||||
del self.router_ndp_proxy_mapping[router_id]
|
||||
|
||||
|
||||
class NDPProxyAgentExtension(l3_extension.L3AgentExtension):
|
||||
|
||||
def initialize(self, connection, driver_type):
|
||||
self.resource_rpc = resources_rpc.ResourcesPullRpcApi()
|
||||
self._register_rpc_consumers()
|
||||
self.mapping = RouterNDPProxyMapping()
|
||||
|
||||
def consume_api(self, agent_api):
|
||||
self.agent_api = agent_api
|
||||
|
||||
def _register_rpc_consumers(self):
|
||||
registry.register(self._handle_notification, resources.NDPPROXY)
|
||||
self._connection = n_rpc.Connection()
|
||||
endpoints = [resources_rpc.ResourcesPushRpcCallback()]
|
||||
topic = resources_rpc.resource_type_versioned_topic(
|
||||
resources.NDPPROXY)
|
||||
self._connection.create_consumer(topic, endpoints, fanout=True)
|
||||
self._connection.consume_in_threads()
|
||||
|
||||
def _handle_notification(self, context, resource_type,
|
||||
ndp_proxies, event_type):
|
||||
for ndp_proxy in ndp_proxies:
|
||||
ri = self.agent_api.get_router_info(
|
||||
ndp_proxy.router_id)
|
||||
if not (ri and self._check_if_ri_need_process(ri) and
|
||||
self._check_if_ndp_proxy_need_process(
|
||||
context, ri, ndp_proxy)):
|
||||
continue
|
||||
(interface_name, namespace,
|
||||
iptables_manager) = self._get_resource_by_router(ri)
|
||||
agent_mode = ri.agent_conf.agent_mode
|
||||
is_distributed = ri.router.get('distributed')
|
||||
if (is_distributed and
|
||||
agent_mode != constants.L3_AGENT_MODE_DVR_SNAT):
|
||||
rtr_2_fip_dev = ri.fip_ns.get_rtr_2_fip_device(ri)
|
||||
fip_2_rtr_dev = ri.fip_ns.get_fip_2_rtr_device(ri)
|
||||
if event_type == events.CREATED:
|
||||
LOG.debug("Create ndp proxy: %s.", ndp_proxy)
|
||||
if (is_distributed and
|
||||
agent_mode != constants.L3_AGENT_MODE_DVR_SNAT):
|
||||
self._process_create_dvr([ndp_proxy], rtr_2_fip_dev,
|
||||
fip_2_rtr_dev, interface_name,
|
||||
namespace)
|
||||
else:
|
||||
self._process_create([ndp_proxy], interface_name,
|
||||
namespace, iptables_manager)
|
||||
self.mapping.set_ndp_proxies([ndp_proxy])
|
||||
elif event_type == events.DELETED:
|
||||
LOG.debug("Delete ndp proxy: %s.", ndp_proxy)
|
||||
if (is_distributed and
|
||||
agent_mode != constants.L3_AGENT_MODE_DVR_SNAT):
|
||||
self._process_delete_dvr([ndp_proxy], rtr_2_fip_dev,
|
||||
fip_2_rtr_dev, interface_name,
|
||||
namespace)
|
||||
else:
|
||||
self._process_delete([ndp_proxy], interface_name,
|
||||
namespace, iptables_manager)
|
||||
self.mapping.del_ndp_proxies([ndp_proxy])
|
||||
if iptables_manager:
|
||||
iptables_manager.apply()
|
||||
|
||||
def _check_if_ri_need_process(self, ri):
|
||||
if not (ri and ri.get_ex_gw_port()):
|
||||
return False
|
||||
is_distributed = ri.router.get('distributed')
|
||||
agent_mode = ri.agent_conf.agent_mode
|
||||
if (is_distributed and
|
||||
agent_mode == constants.L3_AGENT_MODE_DVR_NO_EXTERNAL):
|
||||
return False
|
||||
if is_distributed and agent_mode == constants.L3_AGENT_MODE_DVR_SNAT:
|
||||
if ri.router.get('gw_port_host') != ri.agent_conf.host:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _check_if_ndp_proxy_need_process(self, context, ri, ndp_proxy):
|
||||
"""Check the ndp proxy whether need processed by local l3 agent"""
|
||||
agent_mode = ri.agent_conf.agent_mode
|
||||
is_distributed = ri.router.get('distributed')
|
||||
if not is_distributed:
|
||||
return True
|
||||
# dvr_no_external agent don't need process dvr router's ndp proxy
|
||||
if agent_mode == constants.L3_AGENT_MODE_DVR_NO_EXTERNAL:
|
||||
return False
|
||||
port_obj = self.resource_rpc.bulk_pull(
|
||||
context, resources.PORT, filter_kwargs={
|
||||
'id': ndp_proxy['port_id']})[0]
|
||||
if len(port_obj.bindings) != 1:
|
||||
return False
|
||||
if agent_mode == constants.L3_AGENT_MODE_DVR:
|
||||
if port_obj.bindings[0].host == ri.agent_conf.host:
|
||||
return True
|
||||
# If the l3 agent mode is dvr_no_external of the host which the ndp
|
||||
# proxy's port binding to, the rules related the ndp proxy should be
|
||||
# applied in snat-namespace
|
||||
if agent_mode == constants.L3_AGENT_MODE_DVR_SNAT:
|
||||
agent_obj = self.resource_rpc.bulk_pull(
|
||||
context, resources.AGENT,
|
||||
filter_kwargs={
|
||||
'host': port_obj.bindings[0].host,
|
||||
'agent_type': constants.AGENT_TYPE_L3})[0]
|
||||
if agent_obj.configurations['agent_mode'] == \
|
||||
constants.L3_AGENT_MODE_DVR_NO_EXTERNAL:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_resource_by_router(self, ri):
|
||||
is_distributed = ri.router.get('distributed')
|
||||
ex_gw_port = ri.get_ex_gw_port()
|
||||
if not is_distributed:
|
||||
interface_name = ri.get_external_device_interface_name(ex_gw_port)
|
||||
namespace = ri.ns_name
|
||||
iptables_manager = ri.iptables_manager
|
||||
elif ri.agent_conf.agent_mode == constants.L3_AGENT_MODE_DVR_SNAT:
|
||||
interface_name = ri.get_snat_external_device_interface_name(
|
||||
ex_gw_port)
|
||||
namespace = ri.snat_namespace.name
|
||||
iptables_manager = ri.snat_iptables_manager
|
||||
else:
|
||||
interface_name = ri.fip_ns.get_ext_device_name(
|
||||
ri.fip_ns.agent_gateway_port['id'])
|
||||
namespace = ri.fip_ns.name
|
||||
iptables_manager = None
|
||||
return interface_name, namespace, iptables_manager
|
||||
|
||||
def _get_device_ipv6_lladdr(self, device):
|
||||
lladdr_cidr = ip_lib.get_ipv6_lladdr(device.link.address)
|
||||
return utils.cidr_to_ip(lladdr_cidr)
|
||||
|
||||
@coordination.synchronized('router-lock-ns-{namespace}')
|
||||
def _process_create(self, ndp_proxies, interface_name,
|
||||
namespace, iptables_manager):
|
||||
ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
|
||||
for proxy in ndp_proxies:
|
||||
v6_address = str(proxy.ip_address)
|
||||
cmd = ['ip', '-6', 'neigh', 'add',
|
||||
'proxy', v6_address, 'dev', interface_name]
|
||||
ip_wrapper.netns.execute(cmd, privsep_exec=True)
|
||||
accept_rule = '-i %s --destination %s -j ACCEPT' % (
|
||||
interface_name, v6_address)
|
||||
iptables_manager.ipv6['filter'].add_rule(
|
||||
DEFAULT_NDP_PROXY_CHAIN, accept_rule, top=True)
|
||||
cmd = ['ndsend', v6_address, interface_name]
|
||||
ip_wrapper.netns.execute(cmd, check_exit_code=False,
|
||||
log_fail_as_error=True,
|
||||
privsep_exec=True)
|
||||
|
||||
@coordination.synchronized('router-lock-ns-{namespace}')
|
||||
def _process_create_dvr(self, ndp_proxies, rtr_2_fip_dev,
|
||||
fip_2_rtr_dev, interface_name, namespace):
|
||||
for proxy in ndp_proxies:
|
||||
rtr_2_fip_v6_address = self._get_device_ipv6_lladdr(rtr_2_fip_dev)
|
||||
ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
|
||||
v6_address = str(proxy.ip_address)
|
||||
fip_2_rtr_dev.route.add_route(v6_address, via=rtr_2_fip_v6_address)
|
||||
cmd = ['ip', '-6', 'neigh', 'add',
|
||||
'proxy', v6_address, 'dev', interface_name]
|
||||
ip_wrapper.netns.execute(cmd, privsep_exec=True)
|
||||
cmd = ['ndsend', v6_address, interface_name]
|
||||
ip_wrapper.netns.execute(cmd, check_exit_code=False,
|
||||
log_fail_as_error=True,
|
||||
privsep_exec=True)
|
||||
|
||||
@coordination.synchronized('router-lock-ns-{namespace}')
|
||||
def _process_delete(self, ndp_proxies, interface_name,
|
||||
namespace, iptables_manager):
|
||||
ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
|
||||
for proxy in ndp_proxies:
|
||||
v6_address = str(proxy.ip_address)
|
||||
cmd = ['ip', '-6', 'neigh', 'del',
|
||||
'proxy', v6_address, 'dev', interface_name]
|
||||
ip_wrapper.netns.execute(cmd, privsep_exec=True)
|
||||
accept_rule = '-i %s --destination %s -j ACCEPT' % (
|
||||
interface_name, v6_address)
|
||||
iptables_manager.ipv6['filter'].remove_rule(
|
||||
DEFAULT_NDP_PROXY_CHAIN, accept_rule, top=True)
|
||||
|
||||
@coordination.synchronized('router-lock-ns-{namespace}')
|
||||
def _process_delete_dvr(self, ndp_proxies, rtr_2_fip_dev,
|
||||
fip_2_rtr_dev, interface_name, namespace):
|
||||
rtr_2_fip_v6_address = self._get_device_ipv6_lladdr(rtr_2_fip_dev)
|
||||
ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
|
||||
for proxy in ndp_proxies:
|
||||
v6_address = str(proxy.ip_address)
|
||||
fip_2_rtr_dev.route.delete_route(
|
||||
v6_address, via=rtr_2_fip_v6_address)
|
||||
cmd = ['ip', '-6', 'neigh', 'del',
|
||||
'proxy', v6_address, 'dev', interface_name]
|
||||
ip_wrapper.netns.execute(cmd, privsep_exec=True)
|
||||
|
||||
def _get_router_info(self, router_id):
|
||||
ri = self.agent_api.get_router_info(router_id)
|
||||
if ri:
|
||||
return ri
|
||||
LOG.debug("Router %s is not managed by this agent. "
|
||||
"It was possibly deleted concurrently.", router_id)
|
||||
|
||||
def _check_if_address_scopes_match(self, int_port, ex_gw_port):
|
||||
"""Checks and returns the matching state for v6 scopes."""
|
||||
int_port_addr_scopes = int_port.get('address_scopes', {})
|
||||
ext_port_addr_scopes = ex_gw_port.get('address_scopes', {})
|
||||
key = str(constants.IP_VERSION_6)
|
||||
if int_port_addr_scopes.get(key) == ext_port_addr_scopes.get(key):
|
||||
return True
|
||||
return False
|
||||
|
||||
@coordination.synchronized('router-lock-ns-{namespace}')
|
||||
def _init_ndp_proxy_rule(self, ri, interface_name,
|
||||
iptables_manager, is_distributed, ip_wrapper,
|
||||
namespace):
|
||||
agent_mode = ri.agent_conf.agent_mode
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=1' % interface_name]
|
||||
dvr_with_snat = (
|
||||
is_distributed and agent_mode == constants.L3_AGENT_MODE_DVR_SNAT)
|
||||
if not is_distributed or dvr_with_snat:
|
||||
# We need apply some iptable rules in centralized router namespace.
|
||||
wrap_name = iptables_manager.wrap_name
|
||||
existing_chains = iptables_manager.ipv6['filter'].chains
|
||||
if DEFAULT_NDP_PROXY_CHAIN not in existing_chains:
|
||||
iptables_manager.ipv6['filter'].add_chain(
|
||||
DEFAULT_NDP_PROXY_CHAIN)
|
||||
default_rule = '-i %s -j DROP' % interface_name
|
||||
iptables_manager.ipv6['filter'].add_rule(
|
||||
DEFAULT_NDP_PROXY_CHAIN, default_rule)
|
||||
iptables_manager.apply()
|
||||
|
||||
new_subnet_cidrs = []
|
||||
for internal_port in ri.internal_ports:
|
||||
if self._check_if_address_scopes_match(
|
||||
internal_port, ri.ex_gw_port):
|
||||
for subnet in internal_port['subnets']:
|
||||
if netaddr.IPNetwork(subnet['cidr']).version == \
|
||||
constants.IP_VERSION_4:
|
||||
continue
|
||||
new_subnet_cidrs.append(subnet['cidr'])
|
||||
existing_subnet_cidrs = []
|
||||
for rule in iptables_manager.ipv6['filter'].rules:
|
||||
if ("-j %s-%s") % (
|
||||
wrap_name, DEFAULT_NDP_PROXY_CHAIN) not in rule.rule:
|
||||
continue
|
||||
rule_lists = rule.rule.split(' ')
|
||||
for item in rule_lists:
|
||||
try:
|
||||
netaddr.IPNetwork(item)
|
||||
except netaddr.core.AddrFormatError:
|
||||
continue
|
||||
existing_subnet_cidrs.append(item)
|
||||
|
||||
need_add = set(new_subnet_cidrs) - set(existing_subnet_cidrs)
|
||||
need_del = set(existing_subnet_cidrs) - set(new_subnet_cidrs)
|
||||
for cidr in need_add:
|
||||
subnet_rule = (
|
||||
'-i %s --destination %s -j '
|
||||
'%s-%s') % (interface_name, cidr,
|
||||
wrap_name, DEFAULT_NDP_PROXY_CHAIN)
|
||||
iptables_manager.ipv6['filter'].add_rule(
|
||||
'FORWARD', subnet_rule)
|
||||
for cidr in need_del:
|
||||
subnet_rule = (
|
||||
'-i %s --destination %s -j '
|
||||
'%s-%s') % (interface_name, cidr,
|
||||
wrap_name, DEFAULT_NDP_PROXY_CHAIN)
|
||||
iptables_manager.ipv6['filter'].remove_rule(
|
||||
'FORWARD', subnet_rule)
|
||||
ip_wrapper.netns.execute(sysctl_cmd, privsep_exec=True)
|
||||
|
||||
def _process_router(self, context, data):
|
||||
state = data.get('enable_ndp_proxy', False)
|
||||
ri = self._get_router_info(data['id'])
|
||||
if not self._check_if_ri_need_process(ri):
|
||||
return
|
||||
agent_mode = ri.agent_conf.agent_mode
|
||||
(interface_name, namespace,
|
||||
iptables_manager) = self._get_resource_by_router(ri)
|
||||
ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
|
||||
is_distributed = ri.router.get('distributed')
|
||||
if is_distributed and agent_mode != constants.L3_AGENT_MODE_DVR_SNAT:
|
||||
rtr_2_fip_dev = ri.fip_ns.get_rtr_2_fip_device(ri)
|
||||
fip_2_rtr_dev = ri.fip_ns.get_fip_2_rtr_device(ri)
|
||||
|
||||
existing_ndp_proxies = self.mapping.get_ndp_proxies_by_router_id(
|
||||
ri.router_id)
|
||||
if state:
|
||||
self._init_ndp_proxy_rule(
|
||||
ri, interface_name, iptables_manager,
|
||||
is_distributed, ip_wrapper, namespace)
|
||||
|
||||
ndp_proxies = self.resource_rpc.bulk_pull(
|
||||
context, resources.NDPPROXY,
|
||||
filter_kwargs={'router_id': [data['id']]})
|
||||
need_create = set(ndp_proxies) - set(existing_ndp_proxies)
|
||||
need_delete = set(existing_ndp_proxies) - set(ndp_proxies)
|
||||
|
||||
def filter_ndp_proxies(ri, ndp_proxies):
|
||||
result = []
|
||||
for ndp_proxy in ndp_proxies:
|
||||
if self._check_if_ndp_proxy_need_process(
|
||||
context, ri, ndp_proxy):
|
||||
result.append(ndp_proxy)
|
||||
return result
|
||||
|
||||
need_create = filter_ndp_proxies(ri, need_create)
|
||||
if is_distributed and agent_mode == constants.L3_AGENT_MODE_DVR:
|
||||
self._process_create_dvr(need_create, rtr_2_fip_dev,
|
||||
fip_2_rtr_dev, interface_name,
|
||||
namespace)
|
||||
self._process_delete_dvr(need_delete, rtr_2_fip_dev,
|
||||
fip_2_rtr_dev, interface_name,
|
||||
namespace)
|
||||
else:
|
||||
self._process_create(need_create, interface_name,
|
||||
namespace, iptables_manager)
|
||||
self._process_delete(need_delete, interface_name,
|
||||
namespace, iptables_manager)
|
||||
self.mapping.set_ndp_proxies(need_create)
|
||||
else:
|
||||
if is_distributed and agent_mode == constants.L3_AGENT_MODE_DVR:
|
||||
self._process_delete_dvr(
|
||||
existing_ndp_proxies, rtr_2_fip_dev,
|
||||
fip_2_rtr_dev, interface_name, namespace)
|
||||
else:
|
||||
self._clear_ndp_proxies(
|
||||
ip_wrapper, iptables_manager,
|
||||
interface_name, namespace)
|
||||
self.mapping.clear_by_router_id(ri.router_id)
|
||||
|
||||
if iptables_manager:
|
||||
iptables_manager.apply()
|
||||
|
||||
@coordination.synchronized('router-lock-ns-{namespace}')
|
||||
def _clear_ndp_proxies(self, ip_wrapper, iptables_manager,
|
||||
interface_name, namespace):
|
||||
cmd = ['ip', '-6', 'neigh', 'flush', 'proxy']
|
||||
ip_wrapper.netns.execute(cmd, check_exit_code=False,
|
||||
privsep_exec=True)
|
||||
iptables_manager.ipv6['filter'].remove_chain(
|
||||
DEFAULT_NDP_PROXY_CHAIN)
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=0' % interface_name]
|
||||
ip_wrapper.netns.execute(sysctl_cmd, privsep_exec=True)
|
||||
|
||||
def add_router(self, context, data):
|
||||
self._process_router(context, data)
|
||||
|
||||
def update_router(self, context, data):
|
||||
self._process_router(context, data)
|
||||
|
||||
def delete_router(self, context, data):
|
||||
# Just process dvr router, clear the fip-namespace related rules
|
||||
if not data['distributed']:
|
||||
return
|
||||
ndp_proxies = self.mapping.get_ndp_proxies_by_router_id(data['id'])
|
||||
if not ndp_proxies:
|
||||
return
|
||||
ri = self._get_router_info(data['id'])
|
||||
(interface_name, namespace,
|
||||
iptables_manager) = self._get_resource_by_router(ri)
|
||||
rtr_2_fip_dev = ri.fip_ns.get_rtr_2_fip_device(ri)
|
||||
fip_2_rtr_dev = ri.fip_ns.get_fip_2_rtr_device(ri)
|
||||
self._process_delete_dvr(ndp_proxies, rtr_2_fip_dev,
|
||||
fip_2_rtr_dev, interface_name, namespace)
|
||||
|
||||
def ha_state_change(self, context, data):
|
||||
if data['state'] == 'backup':
|
||||
return
|
||||
self._process_router(context, data)
|
||||
|
||||
def update_network(self, context, data):
|
||||
pass
|
@ -12,9 +12,11 @@
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.objects import address_group
|
||||
from neutron.objects import agent
|
||||
from neutron.objects import conntrack_helper
|
||||
from neutron.objects import local_ip
|
||||
from neutron.objects.logapi import logging_resource as log_object
|
||||
from neutron.objects import ndp_proxy
|
||||
from neutron.objects import network
|
||||
from neutron.objects import port_forwarding
|
||||
from neutron.objects import ports
|
||||
@ -38,6 +40,8 @@ PORTFORWARDING = port_forwarding.PortForwarding.obj_name()
|
||||
CONNTRACKHELPER = conntrack_helper.ConntrackHelper.obj_name()
|
||||
ADDRESSGROUP = address_group.AddressGroup.obj_name()
|
||||
LOCAL_IP_ASSOCIATION = local_ip.LocalIPAssociation.obj_name()
|
||||
NDPPROXY = ndp_proxy.NDPProxy.obj_name()
|
||||
AGENT = agent.Agent.obj_name()
|
||||
|
||||
|
||||
_VALID_CLS = (
|
||||
@ -54,6 +58,8 @@ _VALID_CLS = (
|
||||
conntrack_helper.ConntrackHelper,
|
||||
address_group.AddressGroup,
|
||||
local_ip.LocalIPAssociation,
|
||||
ndp_proxy.NDPProxy,
|
||||
agent.Agent,
|
||||
)
|
||||
|
||||
_TYPE_TO_CLS_MAP = {cls.obj_name(): cls for cls in _VALID_CLS}
|
||||
|
@ -0,0 +1,310 @@
|
||||
# Copyright 2021 Troila
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import netaddr
|
||||
from neutron_lib import constants
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.agent.l3 import agent as neutron_l3_agent
|
||||
from neutron.agent.l3.extensions import ndp_proxy as np
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.agent.linux import iptables_manager as iptable_mng
|
||||
from neutron.api.rpc.callbacks import resources
|
||||
from neutron.common import utils as common_utils
|
||||
from neutron.objects import agent as agent_obj
|
||||
from neutron.objects import ndp_proxy as np_obj
|
||||
from neutron.objects import ports as ports_obj
|
||||
from neutron.tests.functional.agent.l3 import framework
|
||||
from neutron.tests.functional.agent.l3 import test_dvr_router
|
||||
|
||||
HOSTNAME = 'agent1'
|
||||
|
||||
|
||||
class L3AgentNDPProxyTestFramework(framework.L3AgentTestFramework):
|
||||
|
||||
def setUp(self):
|
||||
super(L3AgentNDPProxyTestFramework, self).setUp()
|
||||
self.conf.set_override('extensions', ['ndp_proxy'], 'agent')
|
||||
self.agent = neutron_l3_agent.L3NATAgentWithStateReport(HOSTNAME,
|
||||
self.conf)
|
||||
self.np_ext = np.NDPProxyAgentExtension()
|
||||
|
||||
port_id1 = uuidutils.generate_uuid()
|
||||
port1_binding = ports_obj.PortBinding(port_id=port_id1,
|
||||
host=self.agent.host)
|
||||
port1_obj = ports_obj.Port(id=port_id1, bindings=[port1_binding])
|
||||
port_id2 = uuidutils.generate_uuid()
|
||||
port2_binding = ports_obj.PortBinding(port_id=port_id1,
|
||||
host='fake_host')
|
||||
port2_obj = ports_obj.Port(id=port_id2, bindings=[port2_binding])
|
||||
self.ports = [port1_obj, port2_obj]
|
||||
self.port_binding_map = {port_id1: port1_binding,
|
||||
port_id2: port2_binding}
|
||||
self.ndpproxy1 = np_obj.NDPProxy(
|
||||
context=None, id=uuidutils.generate_uuid(),
|
||||
router_id=uuidutils.generate_uuid(),
|
||||
port_id=port_id1, ip_address='2002::1:3')
|
||||
self.ndpproxy2 = np_obj.NDPProxy(
|
||||
context=None, id=uuidutils.generate_uuid(),
|
||||
router_id=uuidutils.generate_uuid(),
|
||||
port_id=port_id2, ip_address='2002::1:4')
|
||||
self.ndp_proxies = [self.ndpproxy1, self.ndpproxy2]
|
||||
agent_configurations = {
|
||||
'agent_mode': constants.L3_AGENT_MODE_DVR_NO_EXTERNAL}
|
||||
self.agent_obj = agent_obj.Agent(
|
||||
id=uuidutils.generate_uuid(), host=self.agent.host,
|
||||
agent_type=constants.AGENT_TYPE_L3,
|
||||
configurations=agent_configurations)
|
||||
self._set_pull_mock()
|
||||
|
||||
def _set_pull_mock(self):
|
||||
|
||||
def _bulk_pull_mock(context, resource_type, filter_kwargs=None):
|
||||
if resource_type == resources.PORT:
|
||||
return [port for port in self.ports if
|
||||
port.id == filter_kwargs['id']]
|
||||
if resource_type == resources.AGENT:
|
||||
return [self.agent_obj]
|
||||
if resource_type == resources.NDPPROXY:
|
||||
result = []
|
||||
if 'router_id' in filter_kwargs:
|
||||
for ndp_proxy in self.ndp_proxies:
|
||||
if ndp_proxy.router_id in filter_kwargs['router_id']:
|
||||
result.append(ndp_proxy)
|
||||
return result
|
||||
return self.ndp_proxie
|
||||
|
||||
self.pull = mock.patch('neutron.api.rpc.handlers.resources_rpc.'
|
||||
'ResourcesPullRpcApi.pull').start()
|
||||
self.bulk_pull = mock.patch('neutron.api.rpc.handlers.resources_rpc.'
|
||||
'ResourcesPullRpcApi.bulk_pull').start()
|
||||
self.bulk_pull.side_effect = _bulk_pull_mock
|
||||
|
||||
def _get_existing_ndp_proxies(self, interface_name, namespace):
|
||||
ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
|
||||
cmd = ['ip', '-6', 'neigh', 'list', 'proxy']
|
||||
res = ip_wrapper.netns.execute(cmd)
|
||||
proxies = []
|
||||
for proxy in res.split('\n'):
|
||||
# Exclute null line
|
||||
if proxy:
|
||||
proxy_list = proxy.split(' ')
|
||||
if interface_name in proxy_list:
|
||||
try:
|
||||
if netaddr.IPAddress(proxy_list[0]).version == 6:
|
||||
proxies.append(proxy_list[0])
|
||||
except Exception:
|
||||
pass
|
||||
return proxies
|
||||
|
||||
def _assert_ndp_proxy_kernel_parameter(self, ip_wrapper, interface_name):
|
||||
sysctl_cmd = ['sysctl', '-b',
|
||||
'net.ipv6.conf.%s.proxy_ndp' % interface_name]
|
||||
|
||||
def check_kernel_parameter():
|
||||
res = ip_wrapper.netns.execute(sysctl_cmd, privsep_exec=True)
|
||||
if res == "1":
|
||||
return True
|
||||
|
||||
common_utils.wait_until_true(check_kernel_parameter)
|
||||
|
||||
def _assert_ndp_iptable_chain_is_set(self, iptables_manager,
|
||||
interface_name):
|
||||
rule = '-i %s -j DROP' % interface_name
|
||||
rule_obj = iptable_mng.IptablesRule('NDP', rule, True, False,
|
||||
iptables_manager.wrap_name)
|
||||
|
||||
def check_chain_is_set():
|
||||
existing_chains = iptables_manager.ipv6['filter'].chains
|
||||
if 'NDP' not in existing_chains:
|
||||
return False
|
||||
|
||||
existing_rules = iptables_manager.ipv6['filter'].rules
|
||||
if rule_obj in existing_rules:
|
||||
return True
|
||||
|
||||
common_utils.wait_until_true(check_chain_is_set)
|
||||
|
||||
def _assert_ndp_proxy_state_iptable_rules_is_set(
|
||||
self, ri, iptables_manager, interface_name):
|
||||
wrap_name = iptables_manager.wrap_name
|
||||
expected_rules = []
|
||||
for port in ri.internal_ports:
|
||||
for subnet in port['subnets']:
|
||||
if netaddr.IPNetwork(subnet['cidr']).version == \
|
||||
constants.IP_VERSION_4:
|
||||
continue
|
||||
rule = (
|
||||
'-i %s --destination %s -j '
|
||||
'%s-NDP') % (interface_name,
|
||||
subnet['cidr'],
|
||||
wrap_name)
|
||||
|
||||
rule_obj = iptable_mng.IptablesRule(
|
||||
'FORWARD', rule, True, False, iptables_manager.wrap_name)
|
||||
expected_rules.append(rule_obj)
|
||||
|
||||
def check_rules_is_set():
|
||||
existing_rules = iptables_manager.ipv6['filter'].rules
|
||||
for rule in expected_rules:
|
||||
if rule not in existing_rules:
|
||||
return False
|
||||
return True
|
||||
|
||||
common_utils.wait_until_true(check_rules_is_set)
|
||||
|
||||
def _assect_ndp_proxy_rules_is_set(self, ip_wrapper, iptables_manager,
|
||||
interface_name, namespace):
|
||||
expected_iptable_rules = []
|
||||
expected_proxy_address = []
|
||||
for ndp_proxy in self.ndp_proxies:
|
||||
rule = '-i %s --destination %s -j ACCEPT' % (interface_name,
|
||||
ndp_proxy.ip_address)
|
||||
rule_obj = iptable_mng.IptablesRule('NDP', rule, True, True,
|
||||
iptables_manager.wrap_name)
|
||||
expected_iptable_rules.append(rule_obj)
|
||||
expected_proxy_address.append(str(ndp_proxy.ip_address))
|
||||
|
||||
def check_rules_is_set():
|
||||
existing_iptable_rules = iptables_manager.ipv6['filter'].rules
|
||||
for iptable_rule in expected_iptable_rules:
|
||||
if iptable_rule not in existing_iptable_rules:
|
||||
return False
|
||||
|
||||
existing_proxy_addresses = self._get_existing_ndp_proxies(
|
||||
interface_name, namespace)
|
||||
for address in expected_proxy_address:
|
||||
if address not in existing_proxy_addresses:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
common_utils.wait_until_true(check_rules_is_set)
|
||||
|
||||
|
||||
class TestL3AgentNDPProxyExtension(L3AgentNDPProxyTestFramework):
|
||||
|
||||
def _test_router_ndp_proxy(self, enable_ha):
|
||||
router_info = self.generate_router_info(enable_ha=enable_ha)
|
||||
router_info['enable_ndp_proxy'] = True
|
||||
ri = self.manage_router(self.agent, router_info)
|
||||
for ndp_proxy in self.ndp_proxies:
|
||||
ndp_proxy.router_id = ri.router_id
|
||||
(interface_name, namespace,
|
||||
iptables_manager) = self.np_ext._get_resource_by_router(ri)
|
||||
ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
|
||||
self._assert_ndp_proxy_kernel_parameter(ip_wrapper, interface_name)
|
||||
self._assert_ndp_iptable_chain_is_set(iptables_manager, interface_name)
|
||||
slaac = constants.IPV6_SLAAC
|
||||
slaac_mode = {'ra_mode': slaac, 'address_mode': slaac}
|
||||
self._add_internal_interface_by_subnet(
|
||||
ri.router, count=1, ip_version=constants.IP_VERSION_6,
|
||||
ipv6_subnet_modes=[slaac_mode])
|
||||
self.agent._process_updated_router(ri.router)
|
||||
self._assert_ndp_proxy_state_iptable_rules_is_set(
|
||||
ri, iptables_manager, interface_name)
|
||||
self._assect_ndp_proxy_rules_is_set(
|
||||
ip_wrapper, iptables_manager,
|
||||
interface_name, namespace)
|
||||
ri.router['enable_ndp_proxy'] = False
|
||||
self.agent._process_updated_router(ri.router)
|
||||
|
||||
def test_legacy_router_ndp_proxy(self):
|
||||
self._test_router_ndp_proxy(enable_ha=False)
|
||||
|
||||
def test_ha_router_ndp_proxy(self):
|
||||
self._test_router_ndp_proxy(enable_ha=True)
|
||||
|
||||
|
||||
class TestL3AgentNDPProxyExtensionDVR(test_dvr_router.TestDvrRouter,
|
||||
L3AgentNDPProxyTestFramework):
|
||||
|
||||
def test_local_dvr_router(self):
|
||||
self.agent.conf.agent_mode = constants.L3_AGENT_MODE_DVR
|
||||
router_info = self.generate_dvr_router_info(enable_ha=False)
|
||||
for ndp_proxy in self.ndp_proxies:
|
||||
ndp_proxy.router_id = router_info['id']
|
||||
router_info['enable_ndp_proxy'] = True
|
||||
ri = self.manage_router(self.agent, router_info)
|
||||
(interface_name, namespace,
|
||||
iptables_manager) = self.np_ext._get_resource_by_router(ri)
|
||||
ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
|
||||
self._assert_ndp_proxy_kernel_parameter(ip_wrapper, interface_name)
|
||||
self._assect_ndp_proxy_rules_is_set(ri, interface_name, namespace)
|
||||
|
||||
def test_edge_dvr_router(self):
|
||||
self.agent.conf.agent_mode = constants.L3_AGENT_MODE_DVR_SNAT
|
||||
router_info = self.generate_dvr_router_info(enable_ha=False)
|
||||
for ndp_proxy in self.ndp_proxies:
|
||||
ndp_proxy.router_id = router_info['id']
|
||||
router_info['enable_ndp_proxy'] = True
|
||||
ri = self.manage_router(self.agent, router_info)
|
||||
(interface_name, namespace,
|
||||
iptables_manager) = self.np_ext._get_resource_by_router(ri)
|
||||
ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
|
||||
self._assert_ndp_proxy_kernel_parameter(ip_wrapper, interface_name)
|
||||
self._assert_ndp_iptable_chain_is_set(iptables_manager, interface_name)
|
||||
slaac = constants.IPV6_SLAAC
|
||||
slaac_mode = {'ra_mode': slaac, 'address_mode': slaac}
|
||||
self._add_internal_interface_by_subnet(
|
||||
ri.router, count=1, ip_version=constants.IP_VERSION_6,
|
||||
ipv6_subnet_modes=[slaac_mode])
|
||||
self.agent._process_updated_router(ri.router)
|
||||
self._assert_ndp_proxy_state_iptable_rules_is_set(
|
||||
ri, iptables_manager, interface_name)
|
||||
super(
|
||||
TestL3AgentNDPProxyExtensionDVR,
|
||||
self)._assect_ndp_proxy_rules_is_set(
|
||||
ip_wrapper, iptables_manager,
|
||||
interface_name, namespace)
|
||||
ri.router['enable_ndp_proxy'] = False
|
||||
self.agent._process_updated_router(ri.router)
|
||||
|
||||
def _assect_ndp_proxy_rules_is_set(self, ri, interface_name, namespace):
|
||||
rtr_2_fip_dev = ri.fip_ns.get_rtr_2_fip_device(ri)
|
||||
fip_2_rtr_dev = ri.fip_ns.get_fip_2_rtr_device(ri)
|
||||
rtr_2_fip_v6_address = self.np_ext._get_device_ipv6_lladdr(
|
||||
rtr_2_fip_dev)
|
||||
expected_proxy_address = []
|
||||
expected_routes = []
|
||||
for ndp_proxy in self.ndp_proxies:
|
||||
port_binding_obj = self.port_binding_map.get(ndp_proxy.port_id)
|
||||
if port_binding_obj and port_binding_obj.host == self.agent.host:
|
||||
expected_proxy_address.append(str(ndp_proxy.ip_address))
|
||||
expected_routes.append(
|
||||
{'table': 'main', 'source_prefix': None,
|
||||
'cidr': '%s/128' % ndp_proxy.ip_address,
|
||||
'scope': 'global', 'metric': 1024,
|
||||
'proto': 'static', 'device': fip_2_rtr_dev.name,
|
||||
'via': rtr_2_fip_v6_address})
|
||||
|
||||
def check_rules_is_set():
|
||||
existing_proxy_addresses = self._get_existing_ndp_proxies(
|
||||
interface_name, namespace)
|
||||
for address in expected_proxy_address:
|
||||
if address not in existing_proxy_addresses:
|
||||
return False
|
||||
|
||||
existing_routes = fip_2_rtr_dev.route.list_routes(
|
||||
ip_version=constants.IP_VERSION_6)
|
||||
for route in expected_routes:
|
||||
if route not in existing_routes:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
common_utils.wait_until_true(check_rules_is_set)
|
@ -892,8 +892,11 @@ class TestDvrRouter(DvrRouterTestFramework, framework.L3AgentTestFramework):
|
||||
rfp_device = ip_lib.IPDevice(rfp_device_name,
|
||||
namespace=router1.ns_name)
|
||||
rtr_2_fip, fip_2_rtr = router1.rtr_fip_subnet.get_pair()
|
||||
fpr_device_name = router1.fip_ns.get_int_device_name(router1.router_id)
|
||||
fpr_device = ip_lib.IPDevice(fpr_device_name,
|
||||
namespace=fip_ns_name)
|
||||
self._assert_default_gateway(
|
||||
fip_2_rtr, rfp_device, rfp_device_name)
|
||||
fip_2_rtr, rfp_device, rfp_device_name, fpr_device)
|
||||
|
||||
router1.router[lib_constants.FLOATINGIP_KEY] = []
|
||||
self.agent._process_updated_router(router1.router)
|
||||
@ -914,16 +917,26 @@ class TestDvrRouter(DvrRouterTestFramework, framework.L3AgentTestFramework):
|
||||
self.assertEqual(2, interface_rules_list_count)
|
||||
self.assertEqual(0, fip_rule_count)
|
||||
|
||||
def _assert_default_gateway(self, fip_2_rtr, rfp_device, device_name):
|
||||
def _assert_default_gateway(self, fip_2_rtr, rfp_device,
|
||||
device_name, fpr_device):
|
||||
v6_gateway = utils.cidr_to_ip(
|
||||
ip_lib.get_ipv6_lladdr(fpr_device.link.address))
|
||||
expected_gateway = [{'device': device_name,
|
||||
'cidr': '0.0.0.0/0',
|
||||
'via': str(fip_2_rtr.ip),
|
||||
'table': dvr_fip_ns.FIP_RT_TBL}]
|
||||
listed_routes = rfp_device.route.list_routes(
|
||||
'table': dvr_fip_ns.FIP_RT_TBL},
|
||||
{'device': device_name,
|
||||
'cidr': '::/0',
|
||||
'table': 'main',
|
||||
'via': v6_gateway}]
|
||||
v4_routes = rfp_device.route.list_routes(
|
||||
ip_version=lib_constants.IP_VERSION_4,
|
||||
table=dvr_fip_ns.FIP_RT_TBL,
|
||||
via=str(fip_2_rtr.ip))
|
||||
self._check_routes(expected_gateway, listed_routes)
|
||||
v6_routers = rfp_device.route.list_routes(
|
||||
ip_version=lib_constants.IP_VERSION_6,
|
||||
via=v6_gateway)
|
||||
self._check_routes(expected_gateway, v4_routes + v6_routers)
|
||||
|
||||
def test_dvr_router_rem_fips_on_restarted_agent(self):
|
||||
self.agent.conf.agent_mode = 'dvr_snat'
|
||||
@ -1965,7 +1978,7 @@ class TestDvrRouter(DvrRouterTestFramework, framework.L3AgentTestFramework):
|
||||
namespace=fip_ns_name)
|
||||
rtr_2_fip, fip_2_rtr = router1.rtr_fip_subnet.get_pair()
|
||||
self._assert_default_gateway(
|
||||
fip_2_rtr, rfp_device, rfp_device_name)
|
||||
fip_2_rtr, rfp_device, rfp_device_name, fpr_device)
|
||||
|
||||
# Check if any snat redirect rules in the router namespace exist.
|
||||
ip4_rules_list = ip_lib.list_ip_rules(router1.ns_name,
|
||||
|
740
neutron/tests/unit/agent/l3/extensions/test_ndp_proxy.py
Normal file
740
neutron/tests/unit/agent/l3/extensions/test_ndp_proxy.py
Normal file
@ -0,0 +1,740 @@
|
||||
# Copyright 2021 Troila
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from neutron_lib import constants as lib_const
|
||||
from neutron_lib import context
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.agent.l3 import agent as l3_agent
|
||||
from neutron.agent.l3 import dvr_edge_router
|
||||
from neutron.agent.l3 import dvr_local_router as dvr_router
|
||||
from neutron.agent.l3.extensions import ndp_proxy as np
|
||||
from neutron.agent.l3 import l3_agent_extension_api as l3_ext_api
|
||||
from neutron.agent.l3 import router_info
|
||||
from neutron.agent.linux import iptables_manager
|
||||
from neutron.api.rpc.callbacks.consumer import registry
|
||||
from neutron.api.rpc.callbacks import events
|
||||
from neutron.api.rpc.callbacks import resources
|
||||
from neutron.api.rpc.handlers import resources_rpc
|
||||
from neutron.objects import agent as agent_obj
|
||||
from neutron.objects import ndp_proxy as np_obj
|
||||
from neutron.objects import ports as ports_obj
|
||||
from neutron.tests import base
|
||||
from neutron.tests.unit.agent.l3 import test_agent
|
||||
from neutron.tests.unit.agent.l3 import test_dvr_local_router
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
|
||||
HOSTNAME = 'testhost'
|
||||
|
||||
|
||||
class NDPProxyExtensionTestCaseBase(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(NDPProxyExtensionTestCaseBase, self).setUp()
|
||||
self.context = context.get_admin_context()
|
||||
self.connection = mock.Mock()
|
||||
self.ext_port_id = _uuid()
|
||||
self.ex_net_id = _uuid()
|
||||
self.ex_gw_port = {'id': self.ext_port_id,
|
||||
'network_id': self.ex_net_id,
|
||||
'gw_port_host': HOSTNAME}
|
||||
self.fake_router_id = _uuid()
|
||||
self.port_id = _uuid()
|
||||
self.agent_api = l3_ext_api.L3AgentExtensionAPI(None, None)
|
||||
self.np_ext = np.NDPProxyAgentExtension()
|
||||
self.np_ext.consume_api(self.agent_api)
|
||||
self.np_ext.initialize(
|
||||
self.connection, lib_const.L3_AGENT_MODE)
|
||||
self.ndpproxy = np_obj.NDPProxy(
|
||||
context=None, id=_uuid(),
|
||||
router_id=self.fake_router_id,
|
||||
port_id=self.port_id, ip_address='2002::1:3')
|
||||
port_binding = ports_obj.PortBinding(port_id=self.port_id,
|
||||
host=HOSTNAME)
|
||||
port_obj = ports_obj.Port(id=self.port_id, bindings=[port_binding])
|
||||
self.ndp_proxies = [self.ndpproxy]
|
||||
self.ports = [port_obj]
|
||||
agent_configurations = {
|
||||
'agent_mode': lib_const.L3_AGENT_MODE_DVR_NO_EXTERNAL}
|
||||
self.agent_obj = agent_obj.Agent(
|
||||
id=_uuid(), host=HOSTNAME,
|
||||
agent_type=lib_const.AGENT_TYPE_L3,
|
||||
configurations=agent_configurations)
|
||||
self.ip_wrapper = mock.patch('neutron.agent.linux.'
|
||||
'ip_lib.IPWrapper').start()
|
||||
self._set_pull_mock()
|
||||
|
||||
def _set_pull_mock(self):
|
||||
|
||||
def _bulk_pull_mock(context, resource_type, filter_kwargs=None):
|
||||
if resource_type == resources.PORT:
|
||||
return [port for port in self.ports if
|
||||
port.id == filter_kwargs['id']]
|
||||
if resource_type == resources.AGENT:
|
||||
return [self.agent_obj]
|
||||
if resource_type == resources.NDPPROXY:
|
||||
result = []
|
||||
if 'router_id' in filter_kwargs:
|
||||
for ndp_proxy in self.ndp_proxies:
|
||||
if ndp_proxy.router_id in filter_kwargs['router_id']:
|
||||
result.append(ndp_proxy)
|
||||
return result
|
||||
return self.ndp_proxie
|
||||
|
||||
self.pull = mock.patch('neutron.api.rpc.handlers.resources_rpc.'
|
||||
'ResourcesPullRpcApi.pull').start()
|
||||
self.bulk_pull = mock.patch('neutron.api.rpc.handlers.resources_rpc.'
|
||||
'ResourcesPullRpcApi.bulk_pull').start()
|
||||
self.bulk_pull.side_effect = _bulk_pull_mock
|
||||
|
||||
|
||||
class NDPProxyExtensionDVRTestCase(
|
||||
NDPProxyExtensionTestCaseBase,
|
||||
test_dvr_local_router.TestDvrRouterOperations):
|
||||
|
||||
def setUp(self):
|
||||
super(NDPProxyExtensionDVRTestCase, self).setUp()
|
||||
self.conf.host = HOSTNAME
|
||||
self.conf.agent_mode = lib_const.L3_AGENT_MODE_DVR
|
||||
self.agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
self.add_route = mock.MagicMock()
|
||||
self.delete_route = mock.MagicMock()
|
||||
mock_route_cmd = mock.MagicMock()
|
||||
mock_route_cmd.add_route = self.add_route
|
||||
mock_route_cmd.delete_route = self.delete_route
|
||||
self.mock_ip_dev.route = mock_route_cmd
|
||||
self.lladdr = "fe80::f816:3eff:fe5f:9d67"
|
||||
get_ipv6_lladdr = mock.patch("neutron.agent.linux.ip_lib."
|
||||
"get_ipv6_lladdr").start()
|
||||
get_ipv6_lladdr.return_value = "%s/64" % self.lladdr
|
||||
self.router = {'id': self.fake_router_id,
|
||||
'gw_port': self.ex_gw_port,
|
||||
'ha': False,
|
||||
'distributed': True,
|
||||
'enable_ndp_proxy': True}
|
||||
kwargs = {
|
||||
'agent': self.agent,
|
||||
'router_id': self.fake_router_id,
|
||||
'router': self.router,
|
||||
'agent_conf': self.conf,
|
||||
'interface_driver': mock.Mock()}
|
||||
self.router_info = dvr_router.DvrLocalRouter(HOSTNAME, **kwargs)
|
||||
self.get_router_info = mock.patch(
|
||||
'neutron.agent.l3.l3_agent_extension_api.'
|
||||
'L3AgentExtensionAPI.get_router_info').start()
|
||||
self.get_router_info.return_value = self.router_info
|
||||
self.router_info.fip_ns = self.agent.get_fip_ns(self.ex_net_id)
|
||||
agent_ext_port_id = _uuid()
|
||||
self.router_info.fip_ns.agent_gateway_port = {'id': agent_ext_port_id}
|
||||
self.namespace = "fip-%s" % self.ex_net_id
|
||||
self.agent_ext_dvice = "fg-%s" % agent_ext_port_id[:11]
|
||||
self.ip_wrapper.reset_mock()
|
||||
|
||||
def test_create_router(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
expected_calls = [
|
||||
mock.call('2002::1:3', via=self.lladdr)]
|
||||
self.assertEqual(expected_calls, self.add_route.mock_calls)
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=1' % self.agent_ext_dvice]
|
||||
proxy_cmd = ['ip', '-6', 'neigh', 'add',
|
||||
'proxy', '2002::1:3', 'dev', self.agent_ext_dvice]
|
||||
ndsend_cmd = ['ndsend', '2002::1:3', self.agent_ext_dvice]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(sysctl_cmd, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(proxy_cmd, privsep_exec=True),
|
||||
mock.call().netns.execute(
|
||||
ndsend_cmd, check_exit_code=False,
|
||||
log_fail_as_error=True, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
def test_update_router(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
self.add_route.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
self.np_ext.update_router(self.context, self.router)
|
||||
self.add_route.assert_not_called()
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=1' % self.agent_ext_dvice]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(sysctl_cmd, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
def test_add_ndp_proxy_update_router(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
self.add_route.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
ndpproxy = np_obj.NDPProxy(
|
||||
context=None, id=_uuid(),
|
||||
router_id=self.fake_router_id,
|
||||
port_id=self.port_id, ip_address='2002::1:6')
|
||||
self.ndp_proxies.append(ndpproxy)
|
||||
self.np_ext.update_router(self.context, self.router)
|
||||
expected_calls = [
|
||||
mock.call('2002::1:6', via=self.lladdr)]
|
||||
self.assertEqual(expected_calls, self.add_route.mock_calls)
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=1' % self.agent_ext_dvice]
|
||||
proxy_cmd = ['ip', '-6', 'neigh', 'add',
|
||||
'proxy', '2002::1:6', 'dev', self.agent_ext_dvice]
|
||||
ndsend_cmd = ['ndsend', '2002::1:6', self.agent_ext_dvice]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(sysctl_cmd, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(proxy_cmd, privsep_exec=True),
|
||||
mock.call().netns.execute(
|
||||
ndsend_cmd, check_exit_code=False,
|
||||
log_fail_as_error=True, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
def test_del_ndp_proxy_update_router(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
self.add_route.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
self.ndp_proxies = []
|
||||
self.np_ext.update_router(self.context, self.router)
|
||||
self.add_route.assert_not_called()
|
||||
expected_calls = [
|
||||
mock.call('2002::1:3', via=self.lladdr)]
|
||||
self.assertEqual(expected_calls, self.delete_route.mock_calls)
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=1' % self.agent_ext_dvice]
|
||||
proxy_cmd = ['ip', '-6', 'neigh', 'del',
|
||||
'proxy', '2002::1:3', 'dev', self.agent_ext_dvice]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(sysctl_cmd, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(proxy_cmd, privsep_exec=True)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
def test__handle_notification(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
self.add_route.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
ndpproxy = np_obj.NDPProxy(
|
||||
context=None, id=_uuid(),
|
||||
router_id=self.fake_router_id,
|
||||
port_id=self.port_id, ip_address='2002::1:5')
|
||||
self.np_ext._handle_notification(mock.MagicMock(), mock.MagicMock(),
|
||||
[ndpproxy], events.CREATED)
|
||||
expected_calls = [
|
||||
mock.call('2002::1:5', via=self.lladdr)]
|
||||
self.assertEqual(expected_calls, self.add_route.mock_calls)
|
||||
proxy_cmd = ['ip', '-6', 'neigh', 'add',
|
||||
'proxy', '2002::1:5', 'dev', self.agent_ext_dvice]
|
||||
ndsend_cmd = ['ndsend', '2002::1:5', self.agent_ext_dvice]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(proxy_cmd, privsep_exec=True),
|
||||
mock.call().netns.execute(
|
||||
ndsend_cmd, check_exit_code=False,
|
||||
log_fail_as_error=True, privsep_exec=True)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
self.add_route.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
self.np_ext._handle_notification(mock.MagicMock(), mock.MagicMock(),
|
||||
[ndpproxy], events.DELETED)
|
||||
self.add_route.assert_not_called()
|
||||
expected_calls = [
|
||||
mock.call('2002::1:5', via=self.lladdr)]
|
||||
self.assertEqual(expected_calls, self.delete_route.mock_calls)
|
||||
proxy_cmd = ['ip', '-6', 'neigh', 'del',
|
||||
'proxy', '2002::1:5', 'dev', self.agent_ext_dvice]
|
||||
expceted_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(proxy_cmd, privsep_exec=True)]
|
||||
self.assertEqual(expceted_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
|
||||
class NDPProxyExtensionLegacyDVRNoExternalTestCaseBase(
|
||||
NDPProxyExtensionTestCaseBase,
|
||||
test_agent.BasicRouterOperationsFramework):
|
||||
|
||||
def _mock_iptables_actions(self):
|
||||
self.get_router_info = mock.patch(
|
||||
'neutron.agent.l3.l3_agent_extension_api.'
|
||||
'L3AgentExtensionAPI.get_router_info').start()
|
||||
self.add_chain = mock.patch('neutron.agent.linux.iptables_manager.'
|
||||
'IptablesTable.add_chain').start()
|
||||
self.remove_chain = mock.patch('neutron.agent.linux.iptables_manager.'
|
||||
'IptablesTable.remove_chain').start()
|
||||
self.add_rule = mock.patch('neutron.agent.linux.iptables_manager.'
|
||||
'IptablesTable.add_rule').start()
|
||||
self.remove_rule = mock.patch('neutron.agent.linux.iptables_manager.'
|
||||
'IptablesTable.remove_rule').start()
|
||||
|
||||
def _add_chain_mock(chain):
|
||||
self.iptables_manager.ipv6[
|
||||
'filter'].chains.append(chain)
|
||||
|
||||
def _remove_chain_mock(chain):
|
||||
self.iptables_manager.ipv6[
|
||||
'filter'].chains.remove(chain)
|
||||
|
||||
def _add_rule_mock(chain, rule, top=False):
|
||||
rule_obj = mock.MagicMock()
|
||||
rule_obj.rule = rule
|
||||
self.iptables_manager.ipv6[
|
||||
'filter'].rules.append(rule_obj)
|
||||
|
||||
def _remove_rule_mock(chain, rule, top=False):
|
||||
for rule_obj in self.iptables_manager.ipv6[
|
||||
'filter'].rules:
|
||||
if rule == rule_obj.rule:
|
||||
self.iptables_manager.ipv6[
|
||||
'filter'].rules.remove(rule_obj)
|
||||
break
|
||||
|
||||
self.get_router_info.return_value = self.router_info
|
||||
self.add_chain.side_effect = _add_chain_mock
|
||||
self.remove_chain.side_effect = _remove_chain_mock
|
||||
self.add_rule.side_effect = _add_rule_mock
|
||||
self.remove_rule.side_effect = _remove_rule_mock
|
||||
|
||||
def _test_create_router(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
expected_calls = [mock.call(np.DEFAULT_NDP_PROXY_CHAIN)]
|
||||
self.assertEqual(expected_calls, self.add_chain.mock_calls)
|
||||
default_rule = '-i %s -j DROP' % self.ext_device_name
|
||||
subnet_rule = ('-i %s --destination %s -j %s-%s') % (
|
||||
self.ext_device_name, '2001::1:0/112', self.wrap_name,
|
||||
np.DEFAULT_NDP_PROXY_CHAIN)
|
||||
accept_rule = '-i %s --destination %s -j ACCEPT' % (
|
||||
self.ext_device_name, '2002::1:3')
|
||||
expected_calls = [
|
||||
mock.call(np.DEFAULT_NDP_PROXY_CHAIN, default_rule),
|
||||
mock.call('FORWARD', subnet_rule),
|
||||
mock.call(np.DEFAULT_NDP_PROXY_CHAIN, accept_rule, top=True)]
|
||||
self.assertEqual(expected_calls, self.add_rule.mock_calls)
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=1' % self.ext_device_name]
|
||||
proxy_cmd = ['ip', '-6', 'neigh', 'add', 'proxy',
|
||||
'2002::1:3', 'dev', self.ext_device_name]
|
||||
ndsend_cmd = ['ndsend', '2002::1:3', self.ext_device_name]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(sysctl_cmd, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(proxy_cmd, privsep_exec=True),
|
||||
mock.call().netns.execute(ndsend_cmd, check_exit_code=False,
|
||||
log_fail_as_error=True,
|
||||
privsep_exec=True),
|
||||
mock.call(namespace=self.namespace)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
def _test_update_router(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
self.add_chain.reset_mock()
|
||||
self.add_rule.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
self.np_ext.update_router(self.context, self.router)
|
||||
self.add_chain.assert_not_called()
|
||||
self.add_rule.assert_not_called()
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=1' % self.ext_device_name]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(sysctl_cmd, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call(namespace=self.namespace)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
def _test_add_ndp_proxy_update_router(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
self.add_chain.reset_mock()
|
||||
self.add_rule.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
self.ndp_proxies.append(
|
||||
np_obj.NDPProxy(context=None, id=_uuid(),
|
||||
router_id=self.fake_router_id,
|
||||
port_id=self.port_id,
|
||||
ip_address='2002::1:4'))
|
||||
self.np_ext.update_router(self.context, self.router)
|
||||
self.add_chain.assert_not_called()
|
||||
accept_rule = '-i %s --destination %s -j ACCEPT' % (
|
||||
self.ext_device_name, '2002::1:4')
|
||||
expected_calls = [
|
||||
mock.call(np.DEFAULT_NDP_PROXY_CHAIN, accept_rule, top=True)]
|
||||
self.assertEqual(expected_calls, self.add_rule.mock_calls)
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=1' % self.ext_device_name]
|
||||
proxy_cmd = ['ip', '-6', 'neigh', 'add', 'proxy',
|
||||
'2002::1:4', 'dev', self.ext_device_name]
|
||||
ndsend_cmd = ['ndsend', '2002::1:4', self.ext_device_name]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(sysctl_cmd, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(proxy_cmd, privsep_exec=True),
|
||||
mock.call().netns.execute(ndsend_cmd, check_exit_code=False,
|
||||
log_fail_as_error=True,
|
||||
privsep_exec=True),
|
||||
mock.call(namespace=self.namespace)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
def _test_del_ndp_proxy_update_router(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
self.add_chain.reset_mock()
|
||||
self.add_rule.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
self.ndp_proxies = []
|
||||
self.np_ext.update_router(self.context, self.router)
|
||||
self.add_chain.assert_not_called()
|
||||
self.remove_chain.assert_not_called()
|
||||
self.add_rule.assert_not_called()
|
||||
accept_rule = '-i %s --destination %s -j ACCEPT' % (
|
||||
self.ext_device_name, '2002::1:3')
|
||||
expected_calls = [mock.call(np.DEFAULT_NDP_PROXY_CHAIN,
|
||||
accept_rule, top=True)]
|
||||
self.assertEqual(expected_calls, self.remove_rule.mock_calls)
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=1' % self.ext_device_name]
|
||||
proxy_cmd = ['ip', '-6', 'neigh', 'del',
|
||||
'proxy', '2002::1:3', 'dev', self.ext_device_name]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(sysctl_cmd, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(proxy_cmd, privsep_exec=True)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
def _test_add_subnet_update_router(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
self.add_chain.reset_mock()
|
||||
self.add_rule.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
self.internal_ports.append(
|
||||
{'subnets': [{'cidr': '2001::5:0/112'}]})
|
||||
self.np_ext.update_router(self.context, self.router)
|
||||
self.add_chain.assert_not_called()
|
||||
self.remove_chain.assert_not_called()
|
||||
subnet_rule = ('-i %s --destination 2001::5:0/112 -j %s-%s') % (
|
||||
self.ext_device_name, self.wrap_name,
|
||||
np.DEFAULT_NDP_PROXY_CHAIN)
|
||||
expected_calls = [mock.call('FORWARD', subnet_rule)]
|
||||
self.assertEqual(expected_calls, self.add_rule.mock_calls)
|
||||
self.remove_rule.assert_not_called()
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=1' % self.ext_device_name]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(sysctl_cmd, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call(namespace=self.namespace)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
def _test_remove_subnet_update_router(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
self.add_chain.reset_mock()
|
||||
self.add_rule.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
self.router_info.internal_ports = []
|
||||
self.np_ext.update_router(self.context, self.router)
|
||||
self.add_chain.assert_not_called()
|
||||
self.remove_chain.assert_not_called()
|
||||
self.add_rule.assert_not_called()
|
||||
subnet_rule = ('-i %s --destination %s -j %s-%s') % (
|
||||
self.ext_device_name, '2001::1:0/112', self.wrap_name,
|
||||
np.DEFAULT_NDP_PROXY_CHAIN)
|
||||
expected_calls = [mock.call('FORWARD', subnet_rule)]
|
||||
self.assertEqual(expected_calls, self.remove_rule.mock_calls)
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=1' % self.ext_device_name]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(sysctl_cmd, privsep_exec=True),
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call(namespace=self.namespace)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
def _test_disable_ndp_proxy_update_router(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
self.add_chain.reset_mock()
|
||||
self.add_rule.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
self.router['enable_ndp_proxy'] = False
|
||||
self.np_ext.update_router(self.context, self.router)
|
||||
self.add_chain.assert_not_called()
|
||||
expected_calls = [mock.call(np.DEFAULT_NDP_PROXY_CHAIN)]
|
||||
self.assertEqual(expected_calls, self.remove_chain.mock_calls)
|
||||
self.add_rule.assert_not_called()
|
||||
self.remove_rule.assert_not_called()
|
||||
flush_cmd = ['ip', '-6', 'neigh', 'flush', 'proxy']
|
||||
sysctl_cmd = ['sysctl', '-w',
|
||||
'net.ipv6.conf.%s.proxy_ndp=0' % self.ext_device_name]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(flush_cmd, check_exit_code=False,
|
||||
privsep_exec=True),
|
||||
mock.call().netns.execute(sysctl_cmd, privsep_exec=True)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
def _test__handle_notification(self):
|
||||
self.np_ext.add_router(self.context, self.router)
|
||||
self.add_chain.reset_mock()
|
||||
self.add_rule.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
ndpproxy = np_obj.NDPProxy(
|
||||
context=None, id=_uuid(),
|
||||
router_id=self.fake_router_id,
|
||||
port_id=self.port_id, ip_address='2002::1:5')
|
||||
self.np_ext._handle_notification(mock.MagicMock(), mock.MagicMock(),
|
||||
[ndpproxy], events.CREATED)
|
||||
self.add_chain.assert_not_called()
|
||||
self.remove_chain.assert_not_called()
|
||||
accept_rule = '-i %s --destination %s -j ACCEPT' % (
|
||||
self.ext_device_name, '2002::1:5')
|
||||
expected_calls = [
|
||||
mock.call(np.DEFAULT_NDP_PROXY_CHAIN, accept_rule, top=True)]
|
||||
self.assertEqual(expected_calls, self.add_rule.mock_calls)
|
||||
self.remove_rule.assert_not_called()
|
||||
proxy_cmd = ['ip', '-6', 'neigh', 'add', 'proxy',
|
||||
'2002::1:5', 'dev', self.ext_device_name]
|
||||
ndsend_cmd = ['ndsend', '2002::1:5', self.ext_device_name]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(proxy_cmd, privsep_exec=True),
|
||||
mock.call().netns.execute(ndsend_cmd, check_exit_code=False,
|
||||
log_fail_as_error=True,
|
||||
privsep_exec=True)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
self.add_chain.reset_mock()
|
||||
self.remove_chain.reset_mock()
|
||||
self.add_rule.reset_mock()
|
||||
self.remove_rule.reset_mock()
|
||||
self.ip_wrapper.reset_mock()
|
||||
self.np_ext._handle_notification(mock.MagicMock(), mock.MagicMock(),
|
||||
[ndpproxy], events.DELETED)
|
||||
self.add_chain.assert_not_called()
|
||||
self.remove_chain.assert_not_called()
|
||||
self.add_rule.assert_not_called()
|
||||
accept_rule = ('-i %s --destination %s -j ACCEPT') % (
|
||||
self.ext_device_name, '2002::1:5')
|
||||
expected_calls = [
|
||||
mock.call(np.DEFAULT_NDP_PROXY_CHAIN, accept_rule, top=True)]
|
||||
self.assertEqual(expected_calls, self.remove_rule.mock_calls)
|
||||
proxy_cmd = ['ip', '-6', 'neigh', 'del', 'proxy', '2002::1:5',
|
||||
'dev', self.ext_device_name]
|
||||
expected_calls = [
|
||||
mock.call(namespace=self.namespace),
|
||||
mock.call().netns.execute(proxy_cmd, privsep_exec=True)]
|
||||
self.assertEqual(expected_calls, self.ip_wrapper.mock_calls)
|
||||
|
||||
|
||||
class NDPProxyExtensionLegacyTestCase(
|
||||
NDPProxyExtensionLegacyDVRNoExternalTestCaseBase):
|
||||
def setUp(self):
|
||||
super(NDPProxyExtensionLegacyTestCase, self).setUp()
|
||||
self.conf.host = HOSTNAME
|
||||
self.conf.agent_mode = lib_const.L3_AGENT_MODE_LEGACY
|
||||
self.agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
self.ext_device_name = 'qg-%s' % self.ext_port_id[0:11]
|
||||
self.internal_ports = [{'subnets': [{'cidr': '2001::1:0/112'}]}]
|
||||
self.router = {'id': self.fake_router_id,
|
||||
'gw_port': self.ex_gw_port,
|
||||
'ha': False,
|
||||
'distributed': False,
|
||||
'enable_ndp_proxy': True}
|
||||
self.router_info = router_info.RouterInfo(
|
||||
self.agent, self.fake_router_id, self.router, **self.ri_kwargs)
|
||||
self.iptables_manager = self.router_info.iptables_manager
|
||||
self.router_info.internal_ports = self.internal_ports
|
||||
self.router_info.ex_gw_port = self.ex_gw_port
|
||||
self.iptables_manager.ipv6['filter'].chains = []
|
||||
self.iptables_manager.ipv6['filter'].rules = []
|
||||
self.agent.router_info[self.router['id']] = self.router_info
|
||||
self.wrap_name = self.iptables_manager.wrap_name
|
||||
self.namespace = "qrouter-" + self.fake_router_id
|
||||
self._mock_iptables_actions()
|
||||
self.ip_wrapper.reset_mock()
|
||||
|
||||
def test_create_router(self):
|
||||
self._test_create_router()
|
||||
|
||||
def test_update_router(self):
|
||||
self._test_update_router()
|
||||
|
||||
def test_add_ndp_proxy_update_router(self):
|
||||
self._test_add_ndp_proxy_update_router()
|
||||
|
||||
def test_del_ndp_proxy_update_router(self):
|
||||
self._test_del_ndp_proxy_update_router()
|
||||
|
||||
def test_add_subnet_update_router(self):
|
||||
self._test_add_subnet_update_router()
|
||||
|
||||
def test_remove_subnet_update_router(self):
|
||||
self._test_remove_subnet_update_router()
|
||||
|
||||
def test_disable_ndp_proxy_update_router(self):
|
||||
self._test_disable_ndp_proxy_update_router()
|
||||
|
||||
def test__handle_notification(self):
|
||||
self._test__handle_notification()
|
||||
|
||||
|
||||
class NDPProxyExtensionDVRNoExternalTestCase(
|
||||
NDPProxyExtensionLegacyDVRNoExternalTestCaseBase):
|
||||
def setUp(self):
|
||||
super(NDPProxyExtensionLegacyDVRNoExternalTestCaseBase, self).setUp()
|
||||
self.conf.host = HOSTNAME
|
||||
self.conf.agent_mode = lib_const.L3_AGENT_MODE_DVR_SNAT
|
||||
self.agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
self.ext_device_name = 'qg-%s' % self.ext_port_id[0:11]
|
||||
self.internal_ports = [{'subnets': [{'cidr': '2001::1:0/112'}]}]
|
||||
self.router = {'id': self.fake_router_id,
|
||||
'gw_port': self.ex_gw_port,
|
||||
'gw_port_host': HOSTNAME,
|
||||
'ha': False,
|
||||
'distributed': True,
|
||||
'enable_ndp_proxy': True}
|
||||
interface_driver = mock.Mock()
|
||||
interface_driver.DEV_NAME_LEN = 14
|
||||
kwargs = {
|
||||
'agent': self.agent,
|
||||
'router_id': self.fake_router_id,
|
||||
'router': self.router,
|
||||
'agent_conf': self.conf,
|
||||
'interface_driver': interface_driver}
|
||||
self._mock_load_fip = mock.patch.object(
|
||||
dvr_edge_router.DvrEdgeRouter,
|
||||
'_load_used_fip_information').start()
|
||||
self.router_info = dvr_edge_router.DvrEdgeRouter(HOSTNAME, **kwargs)
|
||||
self.iptables_manager = iptables_manager.IptablesManager(
|
||||
namespace=self.router_info.snat_namespace.name,
|
||||
use_ipv6=self.router_info.use_ipv6)
|
||||
self.router_info.snat_iptables_manager = self.iptables_manager
|
||||
self.router_info.internal_ports = self.internal_ports
|
||||
self.router_info.ex_gw_port = self.ex_gw_port
|
||||
self.iptables_manager.ipv6['filter'].chains = []
|
||||
self.iptables_manager.ipv6['filter'].rules = []
|
||||
self.agent.router_info[self.router['id']] = self.router_info
|
||||
self.wrap_name = self.iptables_manager.wrap_name
|
||||
self.namespace = "snat-" + self.fake_router_id
|
||||
self._mock_iptables_actions()
|
||||
self.ip_wrapper.reset_mock()
|
||||
|
||||
def test_create_router(self):
|
||||
self._test_create_router()
|
||||
|
||||
def test_update_router(self):
|
||||
self._test_update_router()
|
||||
|
||||
def test_add_ndp_proxy_update_router(self):
|
||||
self._test_add_ndp_proxy_update_router()
|
||||
|
||||
def test_del_ndp_proxy_update_router(self):
|
||||
self._test_del_ndp_proxy_update_router()
|
||||
|
||||
def test_add_subnet_update_router(self):
|
||||
self._test_add_subnet_update_router()
|
||||
|
||||
def test_remove_subnet_update_router(self):
|
||||
self._test_remove_subnet_update_router()
|
||||
|
||||
def test_disable_ndp_proxy_update_router(self):
|
||||
self._test_disable_ndp_proxy_update_router()
|
||||
|
||||
def test__handle_notification(self):
|
||||
self._test__handle_notification()
|
||||
|
||||
|
||||
class NDPProxyExtensionInitializeTestCase(NDPProxyExtensionTestCaseBase):
|
||||
|
||||
@mock.patch.object(registry, 'register')
|
||||
@mock.patch.object(resources_rpc, 'ResourcesPushRpcCallback')
|
||||
def test_initialize_subscribed_to_rpc(self, rpc_mock, subscribe_mock):
|
||||
call_to_patch = 'neutron_lib.rpc.Connection'
|
||||
with mock.patch(call_to_patch,
|
||||
return_value=self.connection) as create_connection:
|
||||
self.np_ext.initialize(
|
||||
self.connection, lib_const.L3_AGENT_MODE)
|
||||
create_connection.assert_has_calls([mock.call()])
|
||||
self.connection.create_consumer.assert_has_calls(
|
||||
[mock.call(
|
||||
resources_rpc.resource_type_versioned_topic(
|
||||
resources.NDPPROXY),
|
||||
[rpc_mock()],
|
||||
fanout=True)]
|
||||
)
|
||||
subscribe_mock.assert_called_with(
|
||||
mock.ANY, resources.NDPPROXY)
|
||||
|
||||
|
||||
class RouterNDPProxyMappingTestCase(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(RouterNDPProxyMappingTestCase, self).setUp()
|
||||
self.mapping = np.RouterNDPProxyMapping()
|
||||
self.router1 = _uuid()
|
||||
self.router2 = _uuid()
|
||||
self.ndpproxy1 = np_obj.NDPProxy(
|
||||
context=None, id=_uuid(),
|
||||
router_id=self.router1,
|
||||
port_id=_uuid(), ip_address='2002::1:3')
|
||||
self.ndpproxy2 = np_obj.NDPProxy(
|
||||
context=None, id=_uuid(),
|
||||
router_id=self.router2,
|
||||
port_id=_uuid(), ip_address='2002::1:4')
|
||||
self.ndpproxies = [self.ndpproxy1, self.ndpproxy2]
|
||||
|
||||
def test_set_ndp_proxies(self):
|
||||
self.mapping.set_ndp_proxies(self.ndpproxies)
|
||||
for ndp_proxy in self.ndpproxies:
|
||||
res = self.mapping.get_ndp_proxy(ndp_proxy.id)
|
||||
self.assertEqual(ndp_proxy, res)
|
||||
router1_ndp_proxies = self.mapping.get_ndp_proxies_by_router_id(
|
||||
self.router1)
|
||||
self.assertEqual([self.ndpproxy1], router1_ndp_proxies)
|
||||
router2_ndp_proxies = self.mapping.get_ndp_proxies_by_router_id(
|
||||
self.router2)
|
||||
self.assertEqual([self.ndpproxy2], router2_ndp_proxies)
|
||||
|
||||
def test_del_ndp_proxies(self):
|
||||
self.mapping.set_ndp_proxies(self.ndpproxies)
|
||||
self.mapping.del_ndp_proxies([self.ndpproxy2])
|
||||
res = self.mapping.get_ndp_proxy(self.ndpproxy2.id)
|
||||
self.assertIsNone(res)
|
||||
router1_ndp_proxies = self.mapping.get_ndp_proxies_by_router_id(
|
||||
self.router1)
|
||||
self.assertEqual([self.ndpproxy1], router1_ndp_proxies)
|
||||
router2_ndp_proxies = self.mapping.get_ndp_proxies_by_router_id(
|
||||
self.router2)
|
||||
self.assertEqual([], router2_ndp_proxies)
|
||||
|
||||
def test_clear_by_router_id(self):
|
||||
self.mapping.set_ndp_proxies(self.ndpproxies)
|
||||
self.mapping.clear_by_router_id(self.router1)
|
||||
np1 = self.mapping.get_ndp_proxy(self.ndpproxy1.id)
|
||||
self.assertIsNone(np1)
|
||||
np2 = self.mapping.get_ndp_proxy(self.ndpproxy2.id)
|
||||
self.assertEqual(self.ndpproxy2, np2)
|
||||
router1_ndp_proxies = self.mapping.get_ndp_proxies_by_router_id(
|
||||
self.router1)
|
||||
self.assertEqual([], router1_ndp_proxies)
|
||||
router2_ndp_proxies = self.mapping.get_ndp_proxies_by_router_id(
|
||||
self.router2)
|
||||
self.assertEqual([self.ndpproxy2], router2_ndp_proxies)
|
@ -149,6 +149,10 @@ class BasicRouterOperationsFramework(base.BaseTestCase):
|
||||
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.lladdr = "fe80::f816:3eff:fe5f:9d67"
|
||||
get_ipv6_lladdr = mock.patch("neutron.agent.linux.ip_lib."
|
||||
"get_ipv6_lladdr").start()
|
||||
get_ipv6_lladdr.return_value = "%s/64" % self.lladdr
|
||||
|
||||
self.l3pluginApi_cls_p = mock.patch(
|
||||
'neutron.agent.l3.agent.L3PluginApi')
|
||||
|
@ -43,6 +43,10 @@ class TestDvrFipNs(base.BaseTestCase):
|
||||
self.conf,
|
||||
self.driver,
|
||||
use_ipv6=True)
|
||||
self.lladdr = "fe80::f816:3eff:fe5f:9d67"
|
||||
get_ipv6_lladdr = mock.patch("neutron.agent.linux.ip_lib."
|
||||
"get_ipv6_lladdr").start()
|
||||
get_ipv6_lladdr.return_value = "%s/64" % self.lladdr
|
||||
|
||||
def test_subscribe(self):
|
||||
is_first = self.fip_ns.subscribe(mock.sentinel.external_net_id)
|
||||
@ -276,8 +280,6 @@ class TestDvrFipNs(base.BaseTestCase):
|
||||
ri.ns_name = mock.sentinel.router_ns
|
||||
ri.get_ex_gw_port.return_value = {'mtu': 2000}
|
||||
|
||||
rtr_2_fip_name = self.fip_ns.get_rtr_ext_device_name(ri.router_id)
|
||||
fip_2_rtr_name = self.fip_ns.get_int_device_name(ri.router_id)
|
||||
fip_ns_name = self.fip_ns.get_name()
|
||||
|
||||
self.fip_ns.local_subnets = allocator = mock.Mock()
|
||||
@ -294,8 +296,8 @@ class TestDvrFipNs(base.BaseTestCase):
|
||||
self.fip_ns.create_rtr_2_fip_link(ri)
|
||||
|
||||
if not dev_exists:
|
||||
ip_wrapper.add_veth.assert_called_with(rtr_2_fip_name,
|
||||
fip_2_rtr_name,
|
||||
ip_wrapper.add_veth.assert_called_with(device.name,
|
||||
device.name,
|
||||
fip_ns_name)
|
||||
|
||||
self.assertEqual(2, device.link.set_up.call_count)
|
||||
@ -314,8 +316,10 @@ class TestDvrFipNs(base.BaseTestCase):
|
||||
device.neigh.add.assert_has_calls(expected)
|
||||
self.assertEqual(2, device.neigh.add.call_count)
|
||||
|
||||
device.route.add_gateway.assert_called_once_with(
|
||||
'169.254.31.29', table=16)
|
||||
expected_calls = [mock.call('169.254.31.29', table=16),
|
||||
mock.call(self.lladdr)]
|
||||
self.assertEqual(expected_calls,
|
||||
device.route.add_gateway.mock_calls)
|
||||
self.assertTrue(
|
||||
self.fip_ns._add_rtr_ext_route_rule_to_route_table.called)
|
||||
|
||||
|
@ -140,6 +140,7 @@ neutron.agent.l3.extensions =
|
||||
port_forwarding = neutron.agent.l3.extensions.port_forwarding:PortForwardingAgentExtension
|
||||
snat_log = neutron.agent.l3.extensions.snat_log:SNATLoggingExtension
|
||||
conntrack_helper = neutron.agent.l3.extensions.conntrack_helper:ConntrackHelperAgentExtension
|
||||
ndp_proxy = neutron.agent.l3.extensions.ndp_proxy:NDPProxyAgentExtension
|
||||
neutron.services.logapi.drivers =
|
||||
ovs = neutron.services.logapi.drivers.openvswitch.ovs_firewall_log:OVSFirewallLoggingDriver
|
||||
neutron.qos.agent_drivers =
|
||||
|
Loading…
Reference in New Issue
Block a user