From 6a2ccfac32c2993e11ee9d958886d786914131e8 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 27 Apr 2023 13:53:47 +0000 Subject: [PATCH] Make "project_id" in "L3HARouterNetwork" unique constraint There could be just only one HA network per project. This database enforcement guarantees this limitation. Partial-Bug: #2016198 Change-Id: Ieb8aac6244d384b0af522f9ba145e9367de2c8ef --- neutron/cmd/upgrade_checks/checks.py | 42 +++++++++++++++++++ ...d7_create_l3harouternetwork_project_id_.py | 40 ++++++++++++++++++ .../alembic_migrations/versions/EXPAND_HEAD | 2 +- neutron/db/models/l3ha.py | 5 +++ .../unit/cmd/upgrade_checks/test_checks.py | 19 +++++++++ ...s_unique_per_project-4d02e963cfc8d546.yaml | 8 ++++ 6 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 neutron/db/migration/alembic_migrations/versions/2023.2/expand/682c319773d7_create_l3harouternetwork_project_id_.py create mode 100644 releasenotes/notes/ha_router_networks_unique_per_project-4d02e963cfc8d546.yaml diff --git a/neutron/cmd/upgrade_checks/checks.py b/neutron/cmd/upgrade_checks/checks.py index 80b75c9c3ad..bee6cc26d42 100644 --- a/neutron/cmd/upgrade_checks/checks.py +++ b/neutron/cmd/upgrade_checks/checks.py @@ -33,6 +33,7 @@ from neutron.db.extra_dhcp_opt import models as extra_dhcp_opt_models from neutron.db.models import agent as agent_model from neutron.db.models import external_net from neutron.db.models import l3 as l3_models +from neutron.db.models import l3ha as l3ha_models from neutron.db.models.plugins.ml2 import vlanallocation from neutron.db.models import segment from neutron.db import models_v2 @@ -164,6 +165,16 @@ def get_fip_per_network_without_qos_policies(network_id): return query.count() +def get_duplicated_ha_networks_per_project(): + """Return those HA network reg. that have more than 1 entry per project""" + ctx = context.get_admin_context() + with db_api.CONTEXT_READER.using(ctx): + query = ctx.session.query(l3ha_models.L3HARouterNetwork) + query = query.group_by(l3ha_models.L3HARouterNetwork.project_id) + query = query.having(func.count() > 1) + return query.all() + + class CoreChecks(base.BaseChecks): def get_checks(self): @@ -192,6 +203,8 @@ class CoreChecks(base.BaseChecks): self.floatingip_inherit_qos_from_network), (_('Port extra DHCP options check'), self.extra_dhcp_options_check), + (_('Duplicated HA network per project check'), + self.extra_dhcp_options_check), ] @staticmethod @@ -528,3 +541,32 @@ class CoreChecks(base.BaseChecks): upgradecheck.Code.SUCCESS, _('There are no extra_dhcp_opts with the newline character ' 'in the option name or option value.')) + + @staticmethod + def duplicated_ha_network_per_project_check(checker): + """Check if there are duplicated HA networks per project + + By definition there could be zero or one HA network per project. In + case of having more than one register associated to any existing + project (that should never happen), this check will fail. + """ + if not cfg.CONF.database.connection: + return upgradecheck.Result( + upgradecheck.Code.WARNING, + _("Database connection string is not set. Check for " + "extra_dhcp_opts can't be done.")) + + ha_networks = get_duplicated_ha_networks_per_project() + project_ids = {ha_network['project_id'] for ha_network in ha_networks} + network_ids = {ha_network['network_id'] for ha_network in ha_networks} + if project_ids: + return upgradecheck.Result( + upgradecheck.Code.WARNING, + _('The following projects have duplicated HA networks: ' + '%(project_ids)s. This is the list of duplicated HA ' + 'networks: %(network_ids)s' % + {'project_ids': project_ids, 'network_ids': network_ids})) + + return upgradecheck.Result( + upgradecheck.Code.SUCCESS, + _('There are no duplicated HA networks in the system.')) diff --git a/neutron/db/migration/alembic_migrations/versions/2023.2/expand/682c319773d7_create_l3harouternetwork_project_id_.py b/neutron/db/migration/alembic_migrations/versions/2023.2/expand/682c319773d7_create_l3harouternetwork_project_id_.py new file mode 100644 index 00000000000..c506b2bec67 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/2023.2/expand/682c319773d7_create_l3harouternetwork_project_id_.py @@ -0,0 +1,40 @@ +# Copyright 2023 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 + + +"""Create L3HARouterNetwork.project_id unique constraint + +Revision ID: 682c319773d7 +Revises: 6f1145bff34c +Create Date: 2023-04-27 13:45:05.103963 + +""" + +# revision identifiers, used by Alembic. +revision = '682c319773d7' +down_revision = '6f1145bff34c' + + +TABLE = 'ha_router_networks' +COLUMN = 'project_id' + + +def upgrade(): + op.create_unique_constraint( + constraint_name='uniq_%s0%s' % (TABLE, COLUMN), + table_name=TABLE, + columns=[COLUMN]) diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index 8e31a8bd562..bebfb9acc33 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -6f1145bff34c +682c319773d7 diff --git a/neutron/db/models/l3ha.py b/neutron/db/models/l3ha.py index ab7503644b2..f1edfb204f8 100644 --- a/neutron/db/models/l3ha.py +++ b/neutron/db/models/l3ha.py @@ -67,6 +67,11 @@ class L3HARouterNetwork(model_base.BASEV2, model_base.HasProjectPrimaryKey): """ __tablename__ = 'ha_router_networks' + __table_args__ = ( + sa.UniqueConstraint('project_id', + name='uniq_ha_router_networks0project_id'), + model_base.BASEV2.__table_args__ + ) network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id', ondelete="CASCADE"), diff --git a/neutron/tests/unit/cmd/upgrade_checks/test_checks.py b/neutron/tests/unit/cmd/upgrade_checks/test_checks.py index 69dc4a1d9c2..49034ad91b9 100644 --- a/neutron/tests/unit/cmd/upgrade_checks/test_checks.py +++ b/neutron/tests/unit/cmd/upgrade_checks/test_checks.py @@ -278,3 +278,22 @@ class TestChecks(base.BaseTestCase): opt_value='bar\nbar')] result = checks.CoreChecks.extra_dhcp_options_check(mock.ANY) self.assertEqual(Code.WARNING, result.code) + + @mock.patch.object(checks, 'get_duplicated_ha_networks_per_project') + def test_duplicated_ha_network_per_project_check_success(self, + mock_ha_nets): + mock_ha_nets.return_value = [] + result = checks.CoreChecks.duplicated_ha_network_per_project_check( + mock.ANY) + self.assertEqual(Code.SUCCESS, result.code) + + @mock.patch.object(checks, 'get_duplicated_ha_networks_per_project') + def test_duplicated_ha_network_per_project_check_warning(self, + mock_ha_nets): + mock_ha_nets.return_value = [ + {'project_id': 'project1', 'network_id': 'net1'}, + {'project_id': 'project1', 'network_id': 'net2'}, + ] + result = checks.CoreChecks.duplicated_ha_network_per_project_check( + mock.ANY) + self.assertEqual(Code.WARNING, result.code) diff --git a/releasenotes/notes/ha_router_networks_unique_per_project-4d02e963cfc8d546.yaml b/releasenotes/notes/ha_router_networks_unique_per_project-4d02e963cfc8d546.yaml new file mode 100644 index 00000000000..618f1ff8e51 --- /dev/null +++ b/releasenotes/notes/ha_router_networks_unique_per_project-4d02e963cfc8d546.yaml @@ -0,0 +1,8 @@ +--- +other: + - | + Introduced a database constraint to limit the number of + ``ha_router_networks`` registers per project to one only. This register is + used to bind projects and networks, defining the corresponding network + as high availability (HA) network. By definition, only one HA network per + project can exist.