[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:
Rodolfo Alonso Hernandez 2024-03-04 10:39:30 +00:00
parent 7774317af7
commit b8953b543a
7 changed files with 162 additions and 4 deletions

View File

@ -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_ext_gw_mode
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 logging
from neutron_lib.api.definitions import multiprovidernet
@ -125,6 +126,7 @@ ML2_SUPPORTED_API_EXTENSIONS_OVN_L3 = [
l3_ext_gw_multihoming.ALIAS,
l3_enable_default_route_bfd.ALIAS,
l3_enable_default_route_ecmp.ALIAS,
l3_ext_ha_mode.ALIAS,
]
ML2_SUPPORTED_API_EXTENSIONS = [
address_group.ALIAS,

View 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)

View File

@ -139,7 +139,7 @@ class DriverController(object):
# attributes via the API.
try:
_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
# migrate to a new provider. populate the distributed and ha
# 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'],
'distributed_flag':
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:
LOG.debug("Router %(id)s migrating from %(old)s provider to "
"%(new)s provider.", {'id': payload.resource_id,
@ -224,7 +224,7 @@ class DriverController(object):
driver = self.drivers[provider['provider']]
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."""
distributed = _is_distributed(
router.get('distributed', lib_const.ATTR_NOT_SPECIFIED))
@ -236,6 +236,9 @@ class DriverController(object):
for driver in drivers:
if _is_driver_compatible(distributed, ha, driver):
return driver
if exc:
raise exc
raise NotImplementedError(
_("Could not find a service provider that supports "
"distributed=%(d)s and ha=%(h)s") % {'d': distributed, 'h': ha}

View File

@ -26,6 +26,7 @@ from oslo_utils import excutils
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils
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.extensions import revisions
from neutron.objects import router as l3_obj
@ -37,7 +38,8 @@ LOG = logging.getLogger(__name__)
@registry.has_registry_receivers
class OvnDriver(base.L3ServiceProvider):
class OvnDriver(base.L3ServiceProvider,
ovn_l3_ha.OVN_L3_HA_db_mixin):
ha_support = base.MANDATORY
distributed_support = base.MANDATORY

View 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)

View File

@ -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()

View File

@ -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).