diff --git a/doc/source/admin/config-router-flavor-ovn.rst b/doc/source/admin/config-router-flavor-ovn.rst index 25b167369e5..3bd955d942d 100644 --- a/doc/source/admin/config-router-flavor-ovn.rst +++ b/doc/source/admin/config-router-flavor-ovn.rst @@ -1,17 +1,18 @@ .. _config-router-flavor-ovn: -=================================================== -Creating a L3 OVN router with a user-defined flavor -=================================================== +============================================= +Router flavors with the L3 OVN service plugin +============================================= -In this section we describe the steps necessary to create a router with a user -defined flavor. +In this chapter we give examples on how to create routers with user-defined +flavors. .. note:: The following example refers to a dummy user-defined service provider, which in a real situation must be replaced with user provided code. -#. Add the service provider to neutron.conf: +#. Add service providers to neutron.conf. The second provider is a high + availability version of the first one: .. code-block:: console @@ -24,14 +25,14 @@ defined flavor. .. code-block:: console $ openstack network service provider list - +---------------+--------------+---------+ - | Service Type | Name | Default | - +---------------+--------------+---------+ - | L3_ROUTER_NAT | user-defined | False | - | L3_ROUTER_NAT | ovn | True | - +---------------+--------------+---------+ + +---------------+-----------------+---------+ + | Service Type | Name | Default | + +---------------+-----------------+---------+ + | L3_ROUTER_NAT | user-defined | False | + | L3_ROUTER_NAT | ovn | True | + +---------------+-----------------+---------+ -#. Create a service profile for the router flavor: +#. Create service profiles for the router flavors: .. code-block:: console @@ -47,7 +48,7 @@ defined flavor. | project_id | None | +-------------+--------------------------------------------------------------------+ -#. Create the router flavor: +#. Create the router flavors: .. code-block:: console @@ -57,52 +58,133 @@ defined flavor. +---------------------+------------------------------------------------------+ | description | User-defined flavor for routers in the L3 OVN plugin | | enabled | True | - | id | e47c1c5c-629b-4c48-b49a-78abe6ac7696 | + | id | 65df2587-c535-4c3a-af2f-86b2968a3191 | | name | user-defined-router-flavor | | service_profile_ids | [] | | service_type | L3_ROUTER_NAT | +---------------------+------------------------------------------------------+ -#. Add service profile to router flavor: +#. Add service profile to the router flavors: .. code-block:: console $ openstack network flavor add profile user-defined-router-flavor a717c92c-63f7-47e8-9efb-6ad0d61c4875 -#. Create router specifying user-defined flavor: +#. Create routers specifying user-defined flavors. Please note the `ha` + characteristics of the routers created: .. code-block:: console - $ openstack router create router-of-user-defined-flavor --external-gateway public --flavor-id e47c1c5c-629b-4c48-b49a-78abe6ac7696 --max-width 100 - +-------------------------+------------------------------------------------------------------------+ - | Field | Value | - +-------------------------+------------------------------------------------------------------------+ - | admin_state_up | UP | - | availability_zone_hints | | - | availability_zones | | - | created_at | 2023-05-25T22:34:16Z | - | description | | - | enable_ndp_proxy | None | - | external_gateway_info | {"network_id": "ba485dc9-2459-41c1-9d4f-71914a7fba2a", | - | | "external_fixed_ips": [{"subnet_id": | - | | "2e3adb94-c544-4916-a9fb-27a9dea21820", "ip_address": "172.24.8.69"}, | - | | {"subnet_id": "996ed143-917b-4783-8349-03c6a6d9603e", "ip_address": | - | | "2001:db8::261"}], "enable_snat": true} | - | flavor_id | e47c1c5c-629b-4c48-b49a-78abe6ac7696 | - | id | 9f5fec56-1829-4bad-abe5-7b4221649c8e | - | name | router-of-user-defined-flavor | - | project_id | b807321af03f44dc808ff06bbc845804 | - | revision_number | 3 | - | routes | | - | status | ACTIVE | - | tags | | - | tenant_id | b807321af03f44dc808ff06bbc845804 | - | updated_at | 2023-05-25T22:34:16Z | - +-------------------------+------------------------------------------------------------------------+ + $ openstack router create router-of-user-defined-flavor-noha --no-ha --external-gateway public --flavor-id 65df2587-c535-4c3a-af2f-86b2968a3191 --max-width 100 + +---------------------------+----------------------------------------------------------------------+ + | Field | Value | + +---------------------------+----------------------------------------------------------------------+ + | admin_state_up | UP | + | availability_zone_hints | | + | availability_zones | | + | created_at | 2024-03-27T00:31:56Z | + | description | | + | enable_default_route_bfd | False | + | enable_default_route_ecmp | False | + | enable_ndp_proxy | None | + | external_gateway_info | {"network_id": "f1898eb8-54af-4704-8ce2-cf58d37cd1e1", | + | | "external_fixed_ips": [{"subnet_id": | + | | "5f2b4aac-7ef4-4e8a-bd80-a5e1e640e16b", "ip_address": | + | | "172.24.8.113"}, {"subnet_id": | + | | "07227d2b-f102-4788-97f8-a8e8f1b0f6ae", "ip_address": | + | | "2001:db8::234"}], "enable_snat": true} | + | external_gateways | [{'network_id': 'f1898eb8-54af-4704-8ce2-cf58d37cd1e1', | + | | 'external_fixed_ips': [{'ip_address': '172.24.8.113', 'subnet_id': | + | | '5f2b4aac-7ef4-4e8a-bd80-a5e1e640e16b'}, {'ip_address': | + | | '2001:db8::234', 'subnet_id': | + | | '07227d2b-f102-4788-97f8-a8e8f1b0f6ae'}]}] | + | flavor_id | 65df2587-c535-4c3a-af2f-86b2968a3191 | + | ha | False | + | id | 66399600-d4c6-4d25-a05f-10789bf86b2d | + | name | router-of-user-defined-flavor-noha | + | project_id | d458a40ca6d54aa6b2b92721badc9f48 | + | revision_number | 3 | + | routes | | + | status | ACTIVE | + | tags | | + | tenant_id | d458a40ca6d54aa6b2b92721badc9f48 | + | updated_at | 2024-03-27T00:31:56Z | + +---------------------------+----------------------------------------------------------------------+ + $ openstack router create router-of-user-defined-flavor-ha --ha --external-gateway public --flavor-id 65df2587-c535-4c3a-af2f-86b2968a3191 --max-width 100 + +---------------------------+----------------------------------------------------------------------+ + | Field | Value | + +---------------------------+----------------------------------------------------------------------+ + | admin_state_up | UP | + | availability_zone_hints | | + | availability_zones | | + | created_at | 2024-03-27T00:38:47Z | + | description | | + | enable_default_route_bfd | False | + | enable_default_route_ecmp | False | + | enable_ndp_proxy | None | + | external_gateway_info | {"network_id": "f1898eb8-54af-4704-8ce2-cf58d37cd1e1", | + | | "external_fixed_ips": [{"subnet_id": | + | | "5f2b4aac-7ef4-4e8a-bd80-a5e1e640e16b", "ip_address": | + | | "172.24.8.212"}, {"subnet_id": | + | | "07227d2b-f102-4788-97f8-a8e8f1b0f6ae", "ip_address": | + | | "2001:db8::20a"}], "enable_snat": true} | + | external_gateways | [{'network_id': 'f1898eb8-54af-4704-8ce2-cf58d37cd1e1', | + | | 'external_fixed_ips': [{'ip_address': '172.24.8.212', 'subnet_id': | + | | '5f2b4aac-7ef4-4e8a-bd80-a5e1e640e16b'}, {'ip_address': | + | | '2001:db8::20a', 'subnet_id': | + | | '07227d2b-f102-4788-97f8-a8e8f1b0f6ae'}]}] | + | flavor_id | 65df2587-c535-4c3a-af2f-86b2968a3191 | + | ha | True | + | id | 036e639b-f087-418d-9087-5a94c45453b9 | + | name | router-of-user-defined-flavor-ha | + | project_id | d458a40ca6d54aa6b2b92721badc9f48 | + | revision_number | 3 | + | routes | | + | status | ACTIVE | + | tags | | + | tenant_id | d458a40ca6d54aa6b2b92721badc9f48 | + | updated_at | 2024-03-27T00:38:48Z | + +---------------------------+----------------------------------------------------------------------+ -#. Create an OVN flavor router to verify they co-exist with the user-defined - flavor: + $ openstack router create router-of-user-defined-flavor-noha-implicit --external-gateway public --flavor-id 65df2587-c535-4c3a-af2f-86b2968a3191 --max-width 100 + +---------------------------+----------------------------------------------------------------------+ + | Field | Value | + +---------------------------+----------------------------------------------------------------------+ + | admin_state_up | UP | + | availability_zone_hints | | + | availability_zones | | + | created_at | 2024-03-27T00:40:52Z | + | description | | + | enable_default_route_bfd | False | + | enable_default_route_ecmp | False | + | enable_ndp_proxy | None | + | external_gateway_info | {"network_id": "f1898eb8-54af-4704-8ce2-cf58d37cd1e1", | + | | "external_fixed_ips": [{"subnet_id": | + | | "5f2b4aac-7ef4-4e8a-bd80-a5e1e640e16b", "ip_address": | + | | "172.24.8.80"}, {"subnet_id": | + | | "07227d2b-f102-4788-97f8-a8e8f1b0f6ae", "ip_address": | + | | "2001:db8::19c"}], "enable_snat": true} | + | external_gateways | [{'network_id': 'f1898eb8-54af-4704-8ce2-cf58d37cd1e1', | + | | 'external_fixed_ips': [{'ip_address': '172.24.8.80', 'subnet_id': | + | | '5f2b4aac-7ef4-4e8a-bd80-a5e1e640e16b'}, {'ip_address': | + | | '2001:db8::19c', 'subnet_id': | + | | '07227d2b-f102-4788-97f8-a8e8f1b0f6ae'}]}] | + | flavor_id | 65df2587-c535-4c3a-af2f-86b2968a3191 | + | ha | False | + | id | ad2ab001-fc3a-4a3b-a9f0-8ad4f41f54dc | + | name | router-of-user-defined-flavor-noha-implicit | + | project_id | d458a40ca6d54aa6b2b92721badc9f48 | + | revision_number | 3 | + | routes | | + | status | ACTIVE | + | tags | | + | tenant_id | d458a40ca6d54aa6b2b92721badc9f48 | + | updated_at | 2024-03-27T00:40:53Z | + +---------------------------+----------------------------------------------------------------------+ + +#. Create an OVN flavor router to verify it co-exists with the user-defined + flavors: .. code-block:: console @@ -122,6 +204,7 @@ defined flavor. | | {"subnet_id": "996ed143-917b-4783-8349-03c6a6d9603e", "ip_address": | | | "2001:db8::263"}], "enable_snat": true} | | flavor_id | None | + | ha | True | | id | 21889ed3-b8df-4b0e-9a64-92ba9fab655d | | name | ovn-flavor-router | | project_id | b807321af03f44dc808ff06bbc845804 | @@ -133,16 +216,25 @@ defined flavor. | updated_at | 2023-07-20T23:34:21Z | +-------------------------+------------------------------------------------------------------------+ + .. note:: + OVN routers are natively highly available at the OVN/OVS level, through + the use of BFD monitoring. Neutron doesn't get involved in the high + availability aspect beyond router scheduling. For this reason, the `ha` + attribute is associated to routers of the default OVN flavor and is + always set to `True`. This is done for consistency with user defined + flavors routers for which the `ha` attribute will be `True` or `False`, + depending on the characteristics of the router. #. List routers to verify: .. code-block:: console $ openstack router list - +--------------------------------------+-------------------------------+--------+-------+----------------------------------+ - | ID | Name | Status | State | Project | - +--------------------------------------+-------------------------------+--------+-------+----------------------------------+ - | 21889ed3-b8df-4b0e-9a64-92ba9fab655d | ovn-flavor-router | ACTIVE | UP | b807321af03f44dc808ff06bbc845804 | - | 9f5fec56-1829-4bad-abe5-7b4221649c8e | router-of-user-defined-flavor | ACTIVE | UP | b807321af03f44dc808ff06bbc845804 | - | e9f25566-ff73-4a76-aeb4-969c819f9c47 | router1 | ACTIVE | UP | 1bf97e3957654c0182a48727d619e00f | - +--------------------------------------+-------------------------------+--------+-------+----------------------------------+ + +--------------------------------------+---------------------------------------------+--------+-------+----------------------------------+-------+ + | ID | Name | Status | State | Project | HA | + +--------------------------------------+---------------------------------------------+--------+-------+----------------------------------+-------+ + | 21889ed3-b8df-4b0e-9a64-92ba9fab655d | ovn-flavor-router | ACTIVE | UP | b807321af03f44dc808ff06bbc845804 | True | + | 66399600-d4c6-4d25-a05f-10789bf86b2d | router-of-user-defined-flavor-noha | ACTIVE | UP | d458a40ca6d54aa6b2b92721badc9f48 | False | + | 036e639b-f087-418d-9087-5a94c45453b9 | router-of-user-defined-flavor-ha | ACTIVE | UP | d458a40ca6d54aa6b2b92721badc9f48 | True | + | ad2ab001-fc3a-4a3b-a9f0-8ad4f41f54dc | router-of-user-defined-flavor-noha-implicit | ACTIVE | UP | d458a40ca6d54aa6b2b92721badc9f48 | False | + +--------------------------------------+---------------------------------------------+--------+-------+----------------------------------+-------+ diff --git a/neutron/services/ovn_l3/service_providers/user_defined.py b/neutron/services/ovn_l3/service_providers/user_defined.py index e0d516ea4c3..c0393d3e0ff 100644 --- a/neutron/services/ovn_l3/service_providers/user_defined.py +++ b/neutron/services/ovn_l3/service_providers/user_defined.py @@ -13,14 +13,18 @@ # under the License. +from neutron_lib.api import validators from neutron_lib.callbacks import events +from neutron_lib.callbacks import priority_group from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants as const from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory +from oslo_config import cfg from oslo_log import log as logging +from neutron.db import l3_attrs_db from neutron.services.l3_router.service_providers import base @@ -29,6 +33,7 @@ LOG = logging.getLogger(__name__) @registry.has_registry_receivers class UserDefined(base.L3ServiceProvider): + ha_support = base.OPTIONAL def __init__(self, l3_plugin): super(UserDefined, self).__init__(l3_plugin) @@ -63,6 +68,26 @@ class UserDefined(base.L3ServiceProvider): LOG.debug('Got request to associate user defined flavor to router %s', router) + def _is_ha(self, router): + ha = router.get('ha') + if not validators.is_attr_set(ha): + ha = cfg.CONF.l3_ha + return ha + + @registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) + def _process_precommit_router_create(self, resource, event, trigger, + payload): + router = payload.latest_state + context = payload.context + if not self._is_user_defined_provider(context, router): + return + router_db = payload.metadata['router_db'] + is_ha = self._is_ha(router) + router['ha'] = is_ha + l3_attrs_db.ExtraAttributesMixin.set_extra_attr_value(router_db, + 'ha', is_ha) + @registry.receives(resources.ROUTER, [events.AFTER_CREATE]) def _process_router_create(self, resource, event, trigger, payload=None): router = payload.states[0] diff --git a/neutron/tests/unit/services/ovn_l3/service_providers/test_ovn.py b/neutron/tests/unit/services/ovn_l3/service_providers/test_ovn.py new file mode 100644 index 00000000000..9e68097ba9f --- /dev/null +++ b/neutron/tests/unit/services/ovn_l3/service_providers/test_ovn.py @@ -0,0 +1,54 @@ +# 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.callbacks import events +from oslo_config import cfg + +from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf +from neutron.db.models import l3 +from neutron.db.models import l3_attrs +from neutron.services.ovn_l3.service_providers import ovn +from neutron.tests.unit import testlib_api + +DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2' + + +class TestOVN(testlib_api.SqlTestCase): + + def setUp(self): + super(TestOVN, self).setUp() + self.setup_coreplugin(DB_PLUGIN_KLASS) + self.fake_l3 = mock.MagicMock() + self.provider = ovn.OvnDriver(self.fake_l3) + self.context = mock.MagicMock() + self.router = l3.Router(id='fake-uuid', + flavor_id=None) + self.router['extra_attributes'] = l3_attrs.RouterExtraAttributes() + ovn_conf.register_opts() + cfg.CONF.set_override('enable_distributed_floating_ip', True, + group='ovn') + + @mock.patch('neutron.db.ovn_revision_numbers_db.create_initial_revision') + def test_process_router_create_precommit(self, cir): + router_req = {'id': 'fake-uuid', + 'flavor_id': None} + payload = events.DBEventPayload( + self.context, + resource_id=self.router['id'], + states=(router_req,), + metadata={'router_db': self.router}) + self.provider._process_router_create_precommit('resource', 'event', + self, payload) + cir.assert_called_once() diff --git a/neutron/tests/unit/services/ovn_l3/service_providers/test_user_defined.py b/neutron/tests/unit/services/ovn_l3/service_providers/test_user_defined.py index 135ec55ee13..8f5f61bef37 100644 --- a/neutron/tests/unit/services/ovn_l3/service_providers/test_user_defined.py +++ b/neutron/tests/unit/services/ovn_l3/service_providers/test_user_defined.py @@ -16,8 +16,8 @@ from unittest import mock from neutron_lib.callbacks import events from neutron_lib import constants as const - from neutron.db.models import l3 +from neutron.db.models import l3_attrs from neutron.services.ovn_l3.service_providers import user_defined from neutron.tests.unit import testlib_api @@ -37,6 +37,7 @@ class TestUserDefined(testlib_api.SqlTestCase): self.context = 'fake-context' self.router = l3.Router(id='fake-uuid', flavor_id='fake-uuid') + self.router['extra_attributes'] = l3_attrs.RouterExtraAttributes() self.fake_l3.get_router = mock.MagicMock(return_value=self.router) self.fip = {'router_id': 'fake-uuid'} mock_flavor_plugin = mock.MagicMock() @@ -131,3 +132,30 @@ class TestUserDefined(testlib_api.SqlTestCase): fl_plg.get_flavor.reset_mock() fl_plg.get_flavor_next_provider.reset_mock() log.reset_mock() + + def test__is_ha(self): + # test the positive case + router_req = {'id': 'fake-uuid', + 'flavor_id': 'fake-uuid', + 'ha': True} + self.assertTrue(self.provider._is_ha(router_req)) + + # test the negative case + router_req['ha'] = False + self.assertFalse(self.provider._is_ha(router_req)) + + @mock.patch('neutron.db.l3_attrs_db.get_attr_info') + def test__process_precommit_router_create(self, gai): + gai.return_value = {'ha': {'default': False}} + router_req = {'id': 'fake-uuid', + 'flavor_id': 'fake-uuid', + 'ha': True} + payload = events.DBEventPayload( + self.context, + resource_id=self.router['id'], + states=(router_req,), + metadata={'router_db': self.router}) + self.assertFalse(self.router['extra_attributes'].ha) + self.provider._process_precommit_router_create('resource', 'event', + self, payload) + self.assertTrue(self.router['extra_attributes'].ha)