diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index ae416390415..3bd1cc3f361 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -659cbedf30a1 +21ff98fabab1 diff --git a/neutron/db/migration/alembic_migrations/versions/zed/expand/21ff98fabab1_add_ndp_proxy_constraint.py b/neutron/db/migration/alembic_migrations/versions/zed/expand/21ff98fabab1_add_ndp_proxy_constraint.py new file mode 100644 index 00000000000..b4e5be72bc4 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/zed/expand/21ff98fabab1_add_ndp_proxy_constraint.py @@ -0,0 +1,37 @@ +# Copyright 2022 OpenStack Foundation +# +# 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 alembic import op + + +"""add ndp proxy constraint + +Revision ID: 21ff98fabab1 +Revises: 659cbedf30a1 +Create Date: 2022-05-22 14:16:24.550155 + +""" + +# revision identifiers, used by Alembic. +revision = '21ff98fabab1' +down_revision = '659cbedf30a1' + + +def upgrade(): + op.create_unique_constraint( + 'uniq_ndp_proxy0router_id0ip_address', + 'ndp_proxies', + ['router_id', 'ip_address'] + ) diff --git a/neutron/db/models/ndp_proxy.py b/neutron/db/models/ndp_proxy.py index 2cdf025f71a..7653018a90c 100644 --- a/neutron/db/models/ndp_proxy.py +++ b/neutron/db/models/ndp_proxy.py @@ -28,6 +28,12 @@ class NDPProxy(standard_attr.HasStandardAttributes, model_base.HasProject): __tablename__ = 'ndp_proxies' + __table_args__ = ( + sa.UniqueConstraint( + 'router_id', 'ip_address', + name='uniq_ndp_proxy0router_id0ip_address'), + model_base.BASEV2.__table_args__ + ) name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE)) router_id = sa.Column(sa.String(db_const.UUID_FIELD_SIZE), diff --git a/neutron/services/ndp_proxy/exceptions.py b/neutron/services/ndp_proxy/exceptions.py index 15e5b7e8708..f967783b290 100644 --- a/neutron/services/ndp_proxy/exceptions.py +++ b/neutron/services/ndp_proxy/exceptions.py @@ -37,7 +37,7 @@ class AddressScopeConflict(n_exc.Conflict): class RouterGatewayNotValid(n_exc.Conflict): - message = _("Can not enable ndp proxy no " + message = _("Can not enable ndp proxy on " "router %(router_id)s, %(reason)s.") @@ -62,4 +62,4 @@ class RouterIPv6GatewayInUse(n_exc.Conflict): class NDPProxyNotFound(n_exc.NotFound): - message = _("NDP proxy %(id)s could not be found.") + message = _("Ndp proxy %(id)s could not be found.") diff --git a/neutron/tests/unit/extensions/test_l3_ndp_proxy.py b/neutron/tests/unit/extensions/test_l3_ndp_proxy.py index 6f83c602d33..7f8dd960c95 100644 --- a/neutron/tests/unit/extensions/test_l3_ndp_proxy.py +++ b/neutron/tests/unit/extensions/test_l3_ndp_proxy.py @@ -247,7 +247,7 @@ class L3NDPProxyTestCase(test_address_scope.AddressScopeTestCase, def test_enable_ndp_proxy_without_external_gateway(self): with self.router() as router: router_id = router['router']['id'] - err_msg = ("Can not enable ndp proxy no router %s, The router has " + err_msg = ("Can not enable ndp proxy on router %s, The router has " "no external gateway or the external gateway port has " "no IPv6 address.") % router_id self._update_router(router_id, {'enable_ndp_proxy': True}, @@ -260,7 +260,7 @@ class L3NDPProxyTestCase(test_address_scope.AddressScopeTestCase, self._update_router( router_id, {'external_gateway_info': {'network_id': self.ext_net_id}}) - err_msg = ("Can not enable ndp proxy no router %s, The router's " + err_msg = ("Can not enable ndp proxy on router %s, The router's " "external gateway will be unset.") % router_id self._update_router( router_id, @@ -516,3 +516,10 @@ class L3NDPProxyTestCase(test_address_scope.AddressScopeTestCase, self.assertTrue(router_dict['router']['enable_ndp_proxy']) self._create_ndp_proxy( router_id, port_id) + + def test_create_ndp_proxy_with_duplicated(self): + with self.port(self.private_subnet) as port1: + self._create_ndp_proxy(self.router1_id, port1['port']['id']) + self._create_ndp_proxy( + self.router1_id, port1['port']['id'], + expected_code=exc.HTTPConflict.code) diff --git a/releasenotes/notes/forbid-duplicate-ndp-proxy-entry-28040bc2afb3c1c7.yaml b/releasenotes/notes/forbid-duplicate-ndp-proxy-entry-28040bc2afb3c1c7.yaml new file mode 100644 index 00000000000..a3ff5dde941 --- /dev/null +++ b/releasenotes/notes/forbid-duplicate-ndp-proxy-entry-28040bc2afb3c1c7.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Forbid the creation of a duplicate NDP proxy entry on the same router, + since the IP address of a router is unique and an IPv6 address only needs + one NDP proxy.