From 9b27020a65a5e8d1f6c9dccd352ae00a2c40cf41 Mon Sep 17 00:00:00 2001 From: Yang JianFeng Date: Wed, 5 Aug 2020 00:39:33 +0000 Subject: [PATCH] [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 --- neutron/agent/l3/dvr_fip_ns.py | 24 +- neutron/agent/l3/extensions/ndp_proxy.py | 451 +++++++++++ neutron/api/rpc/callbacks/resources.py | 6 + .../l3/extensions/test_ndp_proxy_extension.py | 310 ++++++++ .../functional/agent/l3/test_dvr_router.py | 25 +- .../agent/l3/extensions/test_ndp_proxy.py | 740 ++++++++++++++++++ neutron/tests/unit/agent/l3/test_agent.py | 4 + .../tests/unit/agent/l3/test_dvr_fip_ns.py | 16 +- setup.cfg | 1 + 9 files changed, 1557 insertions(+), 20 deletions(-) create mode 100644 neutron/agent/l3/extensions/ndp_proxy.py create mode 100644 neutron/tests/functional/agent/l3/extensions/test_ndp_proxy_extension.py create mode 100644 neutron/tests/unit/agent/l3/extensions/test_ndp_proxy.py diff --git a/neutron/agent/l3/dvr_fip_ns.py b/neutron/agent/l3/dvr_fip_ns.py index 6aabaa357f6..0aa98a8796a 100644 --- a/neutron/agent/l3/dvr_fip_ns.py +++ b/neutron/agent/l3/dvr_fip_ns.py @@ -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 diff --git a/neutron/agent/l3/extensions/ndp_proxy.py b/neutron/agent/l3/extensions/ndp_proxy.py new file mode 100644 index 00000000000..648a39433d5 --- /dev/null +++ b/neutron/agent/l3/extensions/ndp_proxy.py @@ -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 diff --git a/neutron/api/rpc/callbacks/resources.py b/neutron/api/rpc/callbacks/resources.py index 0517fe7243c..6ccfc840fef 100644 --- a/neutron/api/rpc/callbacks/resources.py +++ b/neutron/api/rpc/callbacks/resources.py @@ -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} diff --git a/neutron/tests/functional/agent/l3/extensions/test_ndp_proxy_extension.py b/neutron/tests/functional/agent/l3/extensions/test_ndp_proxy_extension.py new file mode 100644 index 00000000000..1439b0dd530 --- /dev/null +++ b/neutron/tests/functional/agent/l3/extensions/test_ndp_proxy_extension.py @@ -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) diff --git a/neutron/tests/functional/agent/l3/test_dvr_router.py b/neutron/tests/functional/agent/l3/test_dvr_router.py index 2d6fe70f94d..3f167812cf2 100644 --- a/neutron/tests/functional/agent/l3/test_dvr_router.py +++ b/neutron/tests/functional/agent/l3/test_dvr_router.py @@ -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, diff --git a/neutron/tests/unit/agent/l3/extensions/test_ndp_proxy.py b/neutron/tests/unit/agent/l3/extensions/test_ndp_proxy.py new file mode 100644 index 00000000000..7813e885b24 --- /dev/null +++ b/neutron/tests/unit/agent/l3/extensions/test_ndp_proxy.py @@ -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) diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index 56b88063549..22477297512 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -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') diff --git a/neutron/tests/unit/agent/l3/test_dvr_fip_ns.py b/neutron/tests/unit/agent/l3/test_dvr_fip_ns.py index 8ef3a96bba8..0d1d6464ee5 100644 --- a/neutron/tests/unit/agent/l3/test_dvr_fip_ns.py +++ b/neutron/tests/unit/agent/l3/test_dvr_fip_ns.py @@ -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) diff --git a/setup.cfg b/setup.cfg index 4fde9eb1c41..71326dc305f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 =