[OVN] Enable "ha" API flag for OVN routers
The "ha" API flag is now enabled for the OVN routers. Because of the current implementation, this flag must be always "True". When a new router is created, this flag is always set. If an OVN router is explicitly created or updated with "--no-ha" (ha=False), the server will raise an InvalidInput exception. Depends-On: https://review.opendev.org/c/openstack/neutron-tempest-plugin/+/911081 Closes-Bug: #2020823 Change-Id: I60ff33680dd5397a226a9051d51bfb0701f862b5
This commit is contained in:
parent
7774317af7
commit
b8953b543a
@ -44,6 +44,7 @@ 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_enable_default_route_ecmp
|
||||||
from neutron_lib.api.definitions import l3_ext_gw_mode
|
from neutron_lib.api.definitions import l3_ext_gw_mode
|
||||||
from neutron_lib.api.definitions import l3_ext_gw_multihoming
|
from neutron_lib.api.definitions import l3_ext_gw_multihoming
|
||||||
|
from neutron_lib.api.definitions import l3_ext_ha_mode
|
||||||
from neutron_lib.api.definitions import l3_flavors
|
from neutron_lib.api.definitions import l3_flavors
|
||||||
from neutron_lib.api.definitions import logging
|
from neutron_lib.api.definitions import logging
|
||||||
from neutron_lib.api.definitions import multiprovidernet
|
from neutron_lib.api.definitions import multiprovidernet
|
||||||
@ -125,6 +126,7 @@ ML2_SUPPORTED_API_EXTENSIONS_OVN_L3 = [
|
|||||||
l3_ext_gw_multihoming.ALIAS,
|
l3_ext_gw_multihoming.ALIAS,
|
||||||
l3_enable_default_route_bfd.ALIAS,
|
l3_enable_default_route_bfd.ALIAS,
|
||||||
l3_enable_default_route_ecmp.ALIAS,
|
l3_enable_default_route_ecmp.ALIAS,
|
||||||
|
l3_ext_ha_mode.ALIAS,
|
||||||
]
|
]
|
||||||
ML2_SUPPORTED_API_EXTENSIONS = [
|
ML2_SUPPORTED_API_EXTENSIONS = [
|
||||||
address_group.ALIAS,
|
address_group.ALIAS,
|
||||||
|
35
neutron/db/ovn_l3_hamode_db.py
Normal file
35
neutron/db/ovn_l3_hamode_db.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Copyright 2024 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# 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 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.db import l3_attrs_db
|
||||||
|
|
||||||
|
|
||||||
|
@registry.has_registry_receivers
|
||||||
|
class OVN_L3_HA_db_mixin(l3_attrs_db.ExtraAttributesMixin):
|
||||||
|
"""Mixin class to add high availability capability to OVN routers."""
|
||||||
|
|
||||||
|
@registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE],
|
||||||
|
priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE)
|
||||||
|
def _precommit_router_create(self, resource, event, trigger, payload):
|
||||||
|
"""Event handler to set ha flag creation."""
|
||||||
|
# NOTE(ralonsoh): OVN L3 router HA flag is mandatory and True always,
|
||||||
|
# enforced by ``OvnDriver.ha_support`` set to ``MANDATORY``. This flag
|
||||||
|
# cannot be updated.
|
||||||
|
router_db = payload.metadata['router_db']
|
||||||
|
self.set_extra_attr_value(router_db, 'ha', True)
|
@ -139,7 +139,7 @@ class DriverController(object):
|
|||||||
# attributes via the API.
|
# attributes via the API.
|
||||||
try:
|
try:
|
||||||
_ensure_driver_supports_request(drv, payload.request_body)
|
_ensure_driver_supports_request(drv, payload.request_body)
|
||||||
except lib_exc.InvalidInput:
|
except lib_exc.InvalidInput as exc:
|
||||||
# the current driver does not support this request, we need to
|
# the current driver does not support this request, we need to
|
||||||
# migrate to a new provider. populate the distributed and ha
|
# migrate to a new provider. populate the distributed and ha
|
||||||
# flags from the previous state if not in the update so we can
|
# flags from the previous state if not in the update so we can
|
||||||
@ -162,7 +162,7 @@ class DriverController(object):
|
|||||||
{'ha_flag': payload.request_body['ha'],
|
{'ha_flag': payload.request_body['ha'],
|
||||||
'distributed_flag':
|
'distributed_flag':
|
||||||
payload.request_body['distributed']})
|
payload.request_body['distributed']})
|
||||||
new_drv = self._attrs_to_driver(payload.request_body)
|
new_drv = self._attrs_to_driver(payload.request_body, exc=exc)
|
||||||
if new_drv:
|
if new_drv:
|
||||||
LOG.debug("Router %(id)s migrating from %(old)s provider to "
|
LOG.debug("Router %(id)s migrating from %(old)s provider to "
|
||||||
"%(new)s provider.", {'id': payload.resource_id,
|
"%(new)s provider.", {'id': payload.resource_id,
|
||||||
@ -224,7 +224,7 @@ class DriverController(object):
|
|||||||
driver = self.drivers[provider['provider']]
|
driver = self.drivers[provider['provider']]
|
||||||
return driver
|
return driver
|
||||||
|
|
||||||
def _attrs_to_driver(self, router):
|
def _attrs_to_driver(self, router, exc=None):
|
||||||
"""Get a provider driver handle based on the ha/distributed flags."""
|
"""Get a provider driver handle based on the ha/distributed flags."""
|
||||||
distributed = _is_distributed(
|
distributed = _is_distributed(
|
||||||
router.get('distributed', lib_const.ATTR_NOT_SPECIFIED))
|
router.get('distributed', lib_const.ATTR_NOT_SPECIFIED))
|
||||||
@ -236,6 +236,9 @@ class DriverController(object):
|
|||||||
for driver in drivers:
|
for driver in drivers:
|
||||||
if _is_driver_compatible(distributed, ha, driver):
|
if _is_driver_compatible(distributed, ha, driver):
|
||||||
return driver
|
return driver
|
||||||
|
|
||||||
|
if exc:
|
||||||
|
raise exc
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
_("Could not find a service provider that supports "
|
_("Could not find a service provider that supports "
|
||||||
"distributed=%(d)s and ha=%(h)s") % {'d': distributed, 'h': ha}
|
"distributed=%(d)s and ha=%(h)s") % {'d': distributed, 'h': ha}
|
||||||
|
@ -26,6 +26,7 @@ from oslo_utils import excutils
|
|||||||
from neutron.common.ovn import constants as ovn_const
|
from neutron.common.ovn import constants as ovn_const
|
||||||
from neutron.common.ovn import utils
|
from neutron.common.ovn import utils
|
||||||
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
||||||
|
from neutron.db import ovn_l3_hamode_db as ovn_l3_ha
|
||||||
from neutron.db import ovn_revision_numbers_db as db_rev
|
from neutron.db import ovn_revision_numbers_db as db_rev
|
||||||
from neutron.extensions import revisions
|
from neutron.extensions import revisions
|
||||||
from neutron.objects import router as l3_obj
|
from neutron.objects import router as l3_obj
|
||||||
@ -37,7 +38,8 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
@registry.has_registry_receivers
|
@registry.has_registry_receivers
|
||||||
class OvnDriver(base.L3ServiceProvider):
|
class OvnDriver(base.L3ServiceProvider,
|
||||||
|
ovn_l3_ha.OVN_L3_HA_db_mixin):
|
||||||
ha_support = base.MANDATORY
|
ha_support = base.MANDATORY
|
||||||
distributed_support = base.MANDATORY
|
distributed_support = base.MANDATORY
|
||||||
|
|
||||||
|
50
neutron/tests/unit/db/test_ovn_l3_hamode_db.py
Normal file
50
neutron/tests/unit/db/test_ovn_l3_hamode_db.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Copyright 2024 Red Hat, Inc.
|
||||||
|
# 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 neutron_lib import context
|
||||||
|
from neutron_lib.db import api as db_api
|
||||||
|
from neutron_lib.plugins import constants as plugin_constants
|
||||||
|
from neutron_lib.plugins import directory
|
||||||
|
|
||||||
|
from neutron.db import ovn_l3_hamode_db
|
||||||
|
from neutron.objects import router as router_obj
|
||||||
|
from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin
|
||||||
|
from neutron.tests.unit.db import test_l3_dvr_db
|
||||||
|
|
||||||
|
|
||||||
|
class FakeOVNL3Plugin(test_l3_dvr_db.FakeL3Plugin,
|
||||||
|
ovn_l3_hamode_db.OVN_L3_HA_db_mixin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OVN_L3_HA_db_mixinTestCase(test_plugin.NeutronDbPluginV2TestCase):
|
||||||
|
|
||||||
|
def setUp(self, **kwargs):
|
||||||
|
super().setUp(plugin='ml2', **kwargs)
|
||||||
|
self.core_plugin = directory.get_plugin()
|
||||||
|
self.ctx = context.get_admin_context()
|
||||||
|
self.mixin = FakeOVNL3Plugin()
|
||||||
|
directory.add_plugin(plugin_constants.L3, self.mixin)
|
||||||
|
|
||||||
|
def _create_router(self, router):
|
||||||
|
with db_api.CONTEXT_WRITER.using(self.ctx):
|
||||||
|
return self.mixin._create_router_db(self.ctx, router, 'foo_tenant')
|
||||||
|
|
||||||
|
def test_create_router(self):
|
||||||
|
router_dict = {'name': 'foo_router', 'admin_state_up': True,
|
||||||
|
'distributed': False}
|
||||||
|
router_db = self._create_router(router_dict)
|
||||||
|
router = router_obj.Router.get_object(self.ctx, id=router_db.id)
|
||||||
|
self.assertTrue(router.extra_attributes.ha)
|
@ -0,0 +1,59 @@
|
|||||||
|
# Copyright 2024 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# 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 neutron_lib.callbacks import registry
|
||||||
|
from neutron_lib import context
|
||||||
|
from neutron_lib import exceptions as lib_exc
|
||||||
|
|
||||||
|
from neutron.services.l3_router.service_providers import driver_controller \
|
||||||
|
as l3_driver_controller
|
||||||
|
from neutron.services.ovn_l3.service_providers import driver_controller
|
||||||
|
from neutron.tests.unit import testlib_api
|
||||||
|
|
||||||
|
|
||||||
|
DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
|
||||||
|
|
||||||
|
|
||||||
|
class TestDriverController(testlib_api.SqlTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDriverController, self).setUp()
|
||||||
|
self.setup_coreplugin(DB_PLUGIN_KLASS)
|
||||||
|
self.fake_l3 = mock.Mock()
|
||||||
|
self.dc = driver_controller.DriverController(self.fake_l3)
|
||||||
|
self.fake_l3.l3_driver_controller = self.dc
|
||||||
|
self.ctx = context.get_admin_context()
|
||||||
|
|
||||||
|
def test__update_router_provider_ha_mandatory(self):
|
||||||
|
test_dc = driver_controller.DriverController(self.fake_l3)
|
||||||
|
with mock.patch.object(registry, "publish") as mock_cb:
|
||||||
|
with mock.patch.object(test_dc, "get_provider_for_router"):
|
||||||
|
with mock.patch.object(
|
||||||
|
l3_driver_controller,
|
||||||
|
"_ensure_driver_supports_request") as _ensure:
|
||||||
|
_ensure.side_effect = lib_exc.InvalidInput(
|
||||||
|
error_message='message')
|
||||||
|
self.assertRaises(
|
||||||
|
lib_exc.InvalidInput,
|
||||||
|
test_dc._update_router_provider,
|
||||||
|
None, None, None,
|
||||||
|
payload=events.DBEventPayload(
|
||||||
|
None, request_body={'ha': False,
|
||||||
|
'distributed': True},
|
||||||
|
states=({'flavor_id': None},))
|
||||||
|
)
|
||||||
|
mock_cb.assert_not_called()
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Enabled the ``ha`` API extension for OVN routers. Now the high
|
||||||
|
availability flag ``ha`` can be set and read for OVN routers. NOTE:
|
||||||
|
currently OVN routers are always HA; the flag is mandatory
|
||||||
|
and cannot be unset (but now can be read).
|
Loading…
Reference in New Issue
Block a user