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: #1877301changes/15/744815/35
parent
999bb965f7
commit
9b27020a65
@ -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
|
@ -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)
|
@ -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):
|
||||