From 89702218db2476a17f6ff36cf51b909db563d887 Mon Sep 17 00:00:00 2001 From: Dmitrii Shcherbakov Date: Wed, 22 Feb 2023 21:11:31 +0300 Subject: [PATCH] Add extra router attributes for ECMP and BFD * enable_default_route_ecmp * enable_default_route_bfd Partial-Bug: #2002687 Change-Id: I3fcd0458d20f20ce40378f90f073f37c41400865 --- neutron/conf/db/l3_extra_gws_db.py | 36 +++++++ neutron/conf/policies/router.py | 32 ++++++ neutron/db/l3_attrs_db.py | 10 +- neutron/db/l3_extra_gws_db.py | 12 +++ .../89c58a70ceba_ecmp_bfd_attributes.py | 39 +++++++ .../alembic_migrations/versions/EXPAND_HEAD | 2 +- neutron/db/models/l3_attrs.py | 6 ++ neutron/objects/router.py | 14 ++- neutron/opts.py | 4 +- .../tests/unit/conf/policies/test_router.py | 100 ++++++++++++++++++ neutron/tests/unit/extensions/test_l3.py | 18 +++- neutron/tests/unit/objects/test_objects.py | 2 +- ...t-route-bfd-and-ecmp-2cbb3be64ee25410.yaml | 8 ++ 13 files changed, 276 insertions(+), 7 deletions(-) create mode 100644 neutron/conf/db/l3_extra_gws_db.py create mode 100644 neutron/db/migration/alembic_migrations/versions/2023.2/expand/89c58a70ceba_ecmp_bfd_attributes.py create mode 100644 releasenotes/notes/enable-default-route-bfd-and-ecmp-2cbb3be64ee25410.yaml diff --git a/neutron/conf/db/l3_extra_gws_db.py b/neutron/conf/db/l3_extra_gws_db.py new file mode 100644 index 00000000000..101005eea1f --- /dev/null +++ b/neutron/conf/db/l3_extra_gws_db.py @@ -0,0 +1,36 @@ +# Copyright (c) 2023 Canonical Ltd. +# +# 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 oslo_config import cfg + +from neutron._i18n import _ + + +L3_EXTRA_GWS_OPTS = [ + cfg.BoolOpt('enable_default_route_ecmp', + default=False, + help=_("Define the default value for " + "enable_default_route_ecmp if not specified on the " + "router.")), + cfg.BoolOpt('enable_default_route_bfd', + default=False, + help=_("Define the default value for " + "enable_default_route_bfd if not specified on the " + "router.")), +] + + +def register_db_l3_extragws_opts(conf=cfg.CONF): + conf.register_opts(L3_EXTRA_GWS_OPTS) diff --git a/neutron/conf/policies/router.py b/neutron/conf/policies/router.py index 81becc67d3f..8ad6fb8a6f4 100644 --- a/neutron/conf/policies/router.py +++ b/neutron/conf/policies/router.py @@ -127,6 +127,22 @@ rules = [ deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY) ), + policy.DocumentedRuleDefault( + name='create_router:enable_default_route_bfd', + check_str=base.ADMIN, + scope_types=['project'], + description=('Specify ``enable_default_route_bfd`` attribute when' + ' creating a router'), + operations=ACTION_POST, + ), + policy.DocumentedRuleDefault( + name='create_router:enable_default_route_ecmp', + check_str=base.ADMIN, + scope_types=['project'], + description=('Specify ``enable_default_route_ecmp`` attribute when' + ' creating a router'), + operations=ACTION_POST, + ), policy.DocumentedRuleDefault( name='get_router', @@ -252,6 +268,22 @@ rules = [ deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY) ), + policy.DocumentedRuleDefault( + name='update_router:enable_default_route_bfd', + check_str=base.ADMIN, + scope_types=['project'], + description=('Specify ``enable_default_route_bfd`` attribute when ' + 'updating a router'), + operations=ACTION_POST, + ), + policy.DocumentedRuleDefault( + name='update_router:enable_default_route_ecmp', + check_str=base.ADMIN, + scope_types=['project'], + description=('Specify ``enable_default_route_ecmp`` attribute when ' + 'updating a router'), + operations=ACTION_POST, + ), policy.DocumentedRuleDefault( name='delete_router', diff --git a/neutron/db/l3_attrs_db.py b/neutron/db/l3_attrs_db.py index 9c0af8660c0..dd165c3a70c 100644 --- a/neutron/db/l3_attrs_db.py +++ b/neutron/db/l3_attrs_db.py @@ -18,9 +18,13 @@ from neutron_lib.db import resource_extend from oslo_config import cfg from neutron._i18n import _ +from neutron.conf.db import l3_extra_gws_db from neutron.db.models import l3_attrs +l3_extra_gws_db.register_db_l3_extragws_opts() + + def get_attr_info(): """Returns api visible attr names and their default values.""" return {'distributed': {'default': cfg.CONF.router_distributed}, @@ -29,7 +33,11 @@ def get_attr_info(): 'availability_zone_hints': { 'default': '[]', 'transform_to_db': az_validator.convert_az_list_to_string, - 'transform_from_db': az_validator.convert_az_string_to_list} + 'transform_from_db': az_validator.convert_az_string_to_list}, + 'enable_default_route_ecmp': { + 'default': cfg.CONF.enable_default_route_ecmp}, + 'enable_default_route_bfd': { + 'default': cfg.CONF.enable_default_route_bfd}, } diff --git a/neutron/db/l3_extra_gws_db.py b/neutron/db/l3_extra_gws_db.py index de24324b562..bec734a53ea 100644 --- a/neutron/db/l3_extra_gws_db.py +++ b/neutron/db/l3_extra_gws_db.py @@ -22,6 +22,8 @@ from neutron.db import l3_gwmode_db from neutron.objects import ports as port_obj from neutron.objects import router as l3_obj from neutron_lib.api.definitions import l3 as l3_apidef +from neutron_lib.api.definitions import l3_enable_default_route_bfd +from neutron_lib.api.definitions import l3_enable_default_route_ecmp from neutron_lib.api.definitions import l3_ext_gw_multihoming from neutron_lib.api import extensions from neutron_lib.callbacks import events @@ -75,6 +77,16 @@ class ExtraGatewaysDbOnlyMixin(l3_gwmode_db.L3_NAT_dbonly_mixin): trigger, payload): self._remove_all_gateways(payload.context, payload.resource_id) + @registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE]) + def _process_bfd_ecmp_request(self, resource, event, trigger, payload): + router = payload.latest_state + router_db = payload.metadata['router_db'] + for attr in (l3_enable_default_route_ecmp.ENABLE_DEFAULT_ROUTE_ECMP, + l3_enable_default_route_bfd.ENABLE_DEFAULT_ROUTE_BFD): + value = router.get(attr) + if value is not None: + self.set_extra_attr_value(router_db, attr, value) + def _add_external_gateways( self, context, router_id, gw_info_list, payload): """Add external gateways to a router.""" diff --git a/neutron/db/migration/alembic_migrations/versions/2023.2/expand/89c58a70ceba_ecmp_bfd_attributes.py b/neutron/db/migration/alembic_migrations/versions/2023.2/expand/89c58a70ceba_ecmp_bfd_attributes.py new file mode 100644 index 00000000000..e4f319b25fb --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/2023.2/expand/89c58a70ceba_ecmp_bfd_attributes.py @@ -0,0 +1,39 @@ +# 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 +import sqlalchemy as sa +from sqlalchemy import sql + + +"""Add ECMP and BFD router-level policy attributes +Revision ID: 89c58a70ceba +Revises: c33da356b165 +Create Date: 2023-02-22 21:08:33.593101 + +""" + +# revision identifiers, used by Alembic. +revision = '89c58a70ceba' +down_revision = 'c33da356b165' + + +def upgrade(): + op.add_column('router_extra_attributes', + sa.Column('enable_default_route_ecmp', sa.Boolean(), + server_default=sql.false(), nullable=False)) + op.add_column('router_extra_attributes', + sa.Column('enable_default_route_bfd', sa.Boolean(), + server_default=sql.false(), nullable=False)) diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index 76acf3517ab..eaf0ee1ee2a 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -c33da356b165 +89c58a70ceba diff --git a/neutron/db/models/l3_attrs.py b/neutron/db/models/l3_attrs.py index 6c30ac2c160..34a25c94a3e 100644 --- a/neutron/db/models/l3_attrs.py +++ b/neutron/db/models/l3_attrs.py @@ -40,6 +40,12 @@ class RouterExtraAttributes(model_base.BASEV2): ha_vr_id = sa.Column(sa.Integer()) # Availability Zone support availability_zone_hints = sa.Column(sa.String(255)) + enable_default_route_ecmp = sa.Column(sa.Boolean, default=False, + server_default=sa.sql.false(), + nullable=False) + enable_default_route_bfd = sa.Column(sa.Boolean, default=False, + server_default=sa.sql.false(), + nullable=False) router = orm.relationship( 'Router', load_on_pending=True, diff --git a/neutron/objects/router.py b/neutron/objects/router.py index 5362a11c9b2..2e1c6c18572 100644 --- a/neutron/objects/router.py +++ b/neutron/objects/router.py @@ -71,7 +71,8 @@ class RouterRoute(base.NeutronDbObject): @base.NeutronObjectRegistry.register class RouterExtraAttributes(base.NeutronDbObject): # Version 1.0: Initial version - VERSION = '1.0' + # Version 1.1: Added ECMP and BFD attributes + VERSION = '1.1' db_model = l3_attrs.RouterExtraAttributes @@ -81,7 +82,10 @@ class RouterExtraAttributes(base.NeutronDbObject): 'service_router': obj_fields.BooleanField(default=False), 'ha': obj_fields.BooleanField(default=False), 'ha_vr_id': obj_fields.IntegerField(nullable=True), - 'availability_zone_hints': obj_fields.ListOfStringsField(nullable=True) + 'availability_zone_hints': obj_fields.ListOfStringsField( + nullable=True), + 'enable_default_route_bfd': obj_fields.BooleanField(default=False), + 'enable_default_route_ecmp': obj_fields.BooleanField(default=False), } primary_keys = ['router_id'] @@ -130,6 +134,12 @@ class RouterExtraAttributes(base.NeutronDbObject): return list(query) + def obj_make_compatible(self, primitive, target_version): + _target_version = versionutils.convert_version_to_tuple(target_version) + if _target_version < (1, 1): + primitive.pop('enable_default_route_bfd', None) + primitive.pop('enable_default_route_ecmp', None) + @base.NeutronObjectRegistry.register class RouterPort(base.NeutronDbObject): diff --git a/neutron/opts.py b/neutron/opts.py index 528126e71e1..4cd89fae740 100644 --- a/neutron/opts.py +++ b/neutron/opts.py @@ -35,6 +35,7 @@ import neutron.conf.db.dvr_mac_db import neutron.conf.db.extraroute_db import neutron.conf.db.l3_agentschedulers_db import neutron.conf.db.l3_dvr_db +import neutron.conf.db.l3_extra_gws_db import neutron.conf.db.l3_gwmode_db import neutron.conf.db.l3_hamode_db import neutron.conf.experimental @@ -168,7 +169,8 @@ def list_db_opts(): neutron.conf.db.dvr_mac_db.DVR_MAC_ADDRESS_OPTS, neutron.conf.db.l3_dvr_db.ROUTER_DISTRIBUTED_OPTS, neutron.conf.db.l3_agentschedulers_db.L3_AGENTS_SCHEDULER_OPTS, - neutron.conf.db.l3_hamode_db.L3_HA_OPTS) + neutron.conf.db.l3_hamode_db.L3_HA_OPTS, + neutron.conf.db.l3_extra_gws_db.L3_EXTRA_GWS_OPTS) ), ('database', neutron.db.migration.cli.get_engine_config()) diff --git a/neutron/tests/unit/conf/policies/test_router.py b/neutron/tests/unit/conf/policies/test_router.py index 38bd3834bcc..4ebd2ea1460 100644 --- a/neutron/tests/unit/conf/policies/test_router.py +++ b/neutron/tests/unit/conf/policies/test_router.py @@ -114,6 +114,30 @@ class SystemAdminTests(RouterAPITestCase): 'create_router:external_gateway_info:external_fixed_ips', self.alt_target) + def test_create_router_enable_default_route_bfd(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_router:enable_default_route_bfd', + self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_router:enable_default_route_bfd', + self.alt_target) + + def test_create_router_enable_default_route_ecmp(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_router:enable_default_route_ecmp', + self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_router:enable_default_route_ecmp', + self.alt_target) + def test_get_router(self): self.assertRaises( base_policy.InvalidScope, @@ -224,6 +248,30 @@ class SystemAdminTests(RouterAPITestCase): 'update_router:external_gateway_info:external_fixed_ips', self.alt_target) + def test_update_router_enable_default_route_bfd(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'update_router:enable_default_route_bfd', + self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'update_router:enable_default_route_bfd', + self.alt_target) + + def test_update_router_enable_default_route_ecmp(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'update_router:enable_default_route_ecmp', + self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'update_router:enable_default_route_ecmp', + self.alt_target) + def test_delete_router(self): self.assertRaises( base_policy.InvalidScope, @@ -337,6 +385,30 @@ class AdminTests(RouterAPITestCase): 'create_router:external_gateway_info:external_fixed_ips', self.alt_target)) + def test_update_router_enable_default_route_bfd(self): + self.assertTrue( + policy.enforce( + self.context, + 'update_router:enable_default_route_bfd', + self.target)) + self.assertTrue( + policy.enforce( + self.context, + 'update_router:enable_default_route_bfd', + self.alt_target)) + + def test_update_router_enable_default_route_ecmp(self): + self.assertTrue( + policy.enforce( + self.context, + 'update_router:enable_default_route_ecmp', + self.target)) + self.assertTrue( + policy.enforce( + self.context, + 'update_router:enable_default_route_ecmp', + self.alt_target)) + def test_get_router(self): self.assertTrue( policy.enforce(self.context, 'get_router', self.target)) @@ -524,6 +596,34 @@ class ProjectMemberTests(AdminTests): 'create_router:external_gateway_info:external_fixed_ips', self.alt_target) + def test_update_router_enable_default_route_bfd(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, + 'update_router:enable_default_route_bfd', + self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, + 'update_router:enable_default_route_bfd', + self.alt_target) + + def test_update_router_enable_default_route_ecmp(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, + 'update_router:enable_default_route_ecmp', + self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, + 'update_router:enable_default_route_ecmp', + self.alt_target) + def test_get_router(self): self.assertTrue( policy.enforce(self.context, 'get_router', self.target)) diff --git a/neutron/tests/unit/extensions/test_l3.py b/neutron/tests/unit/extensions/test_l3.py index 1d7e019d1a2..597cb05619e 100644 --- a/neutron/tests/unit/extensions/test_l3.py +++ b/neutron/tests/unit/extensions/test_l3.py @@ -657,9 +657,17 @@ class ExtraAttributesMixinTestCase(testlib_api.SqlTestCase): self.mixin.set_extra_attr_value(self.router, 'availability_zone_hints', ['x', 'y', 'z']) + self.mixin.set_extra_attr_value(self.router, + 'enable_default_route_ecmp', + True) + self.mixin.set_extra_attr_value(self.router, + 'enable_default_route_bfd', + True) expected = self._get_default_api_values() expected.update({'ha_vr_id': 99, - 'availability_zone_hints': ['x', 'y', 'z']}) + 'availability_zone_hints': ['x', 'y', 'z'], + 'enable_default_route_ecmp': True, + 'enable_default_route_bfd': True}) rdict = {} self.mixin._extend_extra_router_dict(rdict, self.router) self.assertEqual(expected, rdict) @@ -667,7 +675,15 @@ class ExtraAttributesMixinTestCase(testlib_api.SqlTestCase): self.mixin.set_extra_attr_value(self.router, 'availability_zone_hints', ['z', 'y', 'z']) + self.mixin.set_extra_attr_value(self.router, + 'enable_default_route_ecmp', + False) + self.mixin.set_extra_attr_value(self.router, + 'enable_default_route_bfd', + False) expected['availability_zone_hints'] = ['z', 'y', 'z'] + expected['enable_default_route_ecmp'] = False + expected['enable_default_route_bfd'] = False self.mixin._extend_extra_router_dict(rdict, self.router) self.assertEqual(expected, rdict) diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index d9f7a17dd9d..be6b76eb3d0 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -106,7 +106,7 @@ object_data = { 'ResourceDelta': '1.0-a980b37e0a52618b5af8db29af18be76', 'Route': '1.0-a9883a63b416126f9e345523ec09483b', 'Router': '1.1-614fa16cc99c60e4fc19ac1b31a52291', - 'RouterExtraAttributes': '1.0-ef8d61ae2864f0ec9af0ab7939cab318', + 'RouterExtraAttributes': '1.1-19c45c32098d2aae8e1a22d18944a954', 'RouterL3AgentBinding': '1.0-c5ba6c95e3a4c1236a55f490cd67da82', 'RouterNDPProxyState': '1.0-4042e475bf173d1d8d17adb962eae1b2', 'RouterPort': '1.0-c8c8f499bcdd59186fcd83f323106908', diff --git a/releasenotes/notes/enable-default-route-bfd-and-ecmp-2cbb3be64ee25410.yaml b/releasenotes/notes/enable-default-route-bfd-and-ecmp-2cbb3be64ee25410.yaml new file mode 100644 index 00000000000..e43923a0129 --- /dev/null +++ b/releasenotes/notes/enable-default-route-bfd-and-ecmp-2cbb3be64ee25410.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``enable_default_route_bfd`` and ``enable_default_route_ecmp`` + configuration options which control default behavior for enabling BFD and + ECMP on default routes for newly created routers. Both configuration + options have a default value of 'False' and are only supported with the + OVN driver.