# 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().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().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]
        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 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 {} --destination {} -j ACCEPT'.format(
            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 {} --destination {} -j ACCEPT'.format(
            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 {} --destination {} -j ACCEPT'.format(
            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 {} --destination {} -j ACCEPT'.format(
            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().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().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)
