Merge "Use the new network HA parameter"

This commit is contained in:
Zuul 2023-08-29 03:34:52 +00:00 committed by Gerrit Code Review
commit f91429100b
10 changed files with 138 additions and 43 deletions

View File

@ -40,6 +40,7 @@ from neutron_lib.api.definitions import l3_ext_gw_mode
from neutron_lib.api.definitions import logging
from neutron_lib.api.definitions import multiprovidernet
from neutron_lib.api.definitions import network_availability_zone
from neutron_lib.api.definitions import network_ha
from neutron_lib.api.definitions import network_ip_availability
from neutron_lib.api.definitions import network_mtu
from neutron_lib.api.definitions import network_mtu_writable
@ -122,6 +123,7 @@ ML2_SUPPORTED_API_EXTENSIONS = [
extra_dhcp_opt.ALIAS,
filter_validation.ALIAS,
multiprovidernet.ALIAS,
network_ha.ALIAS,
network_mtu.ALIAS,
network_mtu_writable.ALIAS,
network_availability_zone.ALIAS,

View File

@ -19,6 +19,7 @@ import random
import netaddr
from neutron_lib.api.definitions import l3 as l3_apidef
from neutron_lib.api.definitions import l3_ext_ha_mode as l3_ext_ha_apidef
from neutron_lib.api.definitions import network_ha as network_ha_apidef
from neutron_lib.api.definitions import port as port_def
from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import provider_net as providernet
@ -63,6 +64,11 @@ LOG = logging.getLogger(__name__)
l3_hamode_db.register_db_l3_hamode_opts()
# TODO(ralonsoh): move to neutron-lib
class DuplicatedHANetwork(n_exc.Conflict):
message = _('Project %(project_id)s already has a HA network.')
@registry.has_registry_receivers
class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin,
router_az_db.RouterAvailabilityZoneMixin):
@ -192,21 +198,21 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin,
return p_utils.create_subnet(self._core_plugin, context,
{'subnet': args})
def _create_ha_network_tenant_binding(self, context, tenant_id,
network_id):
ha_network = l3_hamode.L3HARouterNetwork(
context, project_id=tenant_id, network_id=network_id)
ha_network.create()
# we need to check if someone else just inserted at exactly the
# same time as us because there is no constrain in L3HARouterNetwork
# that prevents multiple networks per tenant
if l3_hamode.L3HARouterNetwork.count(
context, project_id=tenant_id) > 1:
# we need to throw an error so our network is deleted
# and the process is started over where the existing
# network will be selected.
raise db_exc.DBDuplicateEntry(columns=['tenant_id'])
return None, ha_network
@registry.receives(resources.NETWORK, [events.PRECOMMIT_CREATE])
def _create_ha_network_tenant_binding(self, resource, event, trigger,
payload=None):
if not payload.request_body.get(network_ha_apidef.HA):
return
network = payload.latest_state
context = payload.context
ha_network = l3_hamode.L3HARouterNetwork(payload.context,
project_id=context.project_id,
network_id=network['id'])
try:
ha_network.create()
except obj_base.NeutronDbObjectDuplicateEntry:
raise DuplicatedHANetwork(project_id=context.project_id)
def _add_ha_network_settings(self, network):
if cfg.CONF.l3_ha_network_type:
@ -218,29 +224,27 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin,
def _create_ha_network(self, context, tenant_id):
admin_ctx = context.elevated()
# The project ID is needed to create the ``L3HARouterNetwork``
# resource; the project ID cannot be retrieved from the network because
# is explicitly created without it.
admin_ctx.project_id = tenant_id
args = {'network':
{'name': constants.HA_NETWORK_NAME % tenant_id,
'tenant_id': '',
'shared': False,
'admin_state_up': True}}
'admin_state_up': True,
network_ha_apidef.HA: True,
}}
self._add_ha_network_settings(args['network'])
creation = functools.partial(p_utils.create_network,
self._core_plugin, admin_ctx, args)
content = functools.partial(self._create_ha_network_tenant_binding,
admin_ctx, tenant_id)
deletion = functools.partial(self._core_plugin.delete_network,
admin_ctx)
network, ha_network = db_utils.safe_creation(
context, creation, deletion, content, transaction=False)
network = p_utils.create_network(self._core_plugin, admin_ctx, args)
try:
self._create_ha_subnet(admin_ctx, network['id'], tenant_id)
except Exception:
with excutils.save_and_reraise_exception():
self._core_plugin.delete_network(admin_ctx, network['id'])
return ha_network
return l3_hamode.L3HARouterNetwork.get_object(
admin_ctx, network_id=network['id'], project_id=tenant_id)
def get_number_of_agents_for_scheduling(self, context):
"""Return number of agents on which the router will be scheduled."""
@ -370,8 +374,10 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin,
# ensure the HA network exists before we start router creation so
# we can provide meaningful errors back to the user if no network
# can be allocated
if not self.get_ha_network(context, router['tenant_id']):
self._create_ha_network(context, router['tenant_id'])
# TODO(ralonsoh): remove once bp/keystone-v3 migration finishes.
project_id = router.get('project_id') or router['tenant_id']
if not self.get_ha_network(context, project_id):
self._create_ha_network(context, project_id)
@registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE],
priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE)

View File

@ -0,0 +1,21 @@
# Copyright 2023 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.api.definitions import network_ha
from neutron_lib.api import extensions
class Network_ha(extensions.APIExtensionDescriptor):
"""Extension class supporting network HA."""
api_definition = network_ha

View File

@ -25,6 +25,7 @@ from neutron_lib.api.definitions import l3_ext_gw_mode
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_port_ip_change_not_allowed
from neutron_lib.api.definitions import network_ha
from neutron_lib.api.definitions import qos_gateway_ip
from neutron_lib.api.definitions import \
router_admin_state_down_before_update as r_admin_state_down_before_update
@ -109,7 +110,9 @@ class L3RouterPlugin(service_base.ServicePluginBase,
floatingip_pools.ALIAS,
qos_gateway_ip.ALIAS,
l3_port_ip_change_not_allowed.ALIAS,
r_admin_state_down_before_update.ALIAS]
r_admin_state_down_before_update.ALIAS,
network_ha.ALIAS,
]
__native_pagination_support = True
__native_sorting_support = True

View File

@ -16,6 +16,8 @@
import collections
import random
from neutron_lib.api import attributes
from neutron_lib.api.definitions import network_ha
from neutron_lib import constants
from neutron_lib import context
from neutron_lib.plugins import constants as plugin_constants
@ -302,6 +304,10 @@ class L3AZSchedulerBaseTest(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
directory.add_plugin(plugin_constants.L3, self.l3_plugin)
self.adminContext = context.get_admin_context()
self.adminContext.tenant_id = '_func_test_tenant_'
# Extend network HA extension.
rname = network_ha.COLLECTION_NAME
attributes.RESOURCES[rname].update(
network_ha.RESOURCE_ATTRIBUTE_MAP[rname])
def _create_l3_agent(self, host, context, agent_mode='legacy', plugin=None,
state=True, az='nova'):

View File

@ -14,10 +14,12 @@
from unittest import mock
from neutron_lib.api import attributes
from neutron_lib.api.definitions import dvr as dvr_apidef
from neutron_lib.api.definitions import external_net as extnet_apidef
from neutron_lib.api.definitions import l3 as l3_apidef
from neutron_lib.api.definitions import l3_ext_ha_mode
from neutron_lib.api.definitions import network_ha
from neutron_lib.api.definitions import port as port_def
from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import provider_net as providernet
@ -31,7 +33,6 @@ from neutron_lib.db import api as db_api
from neutron_lib import exceptions as n_exc
from neutron_lib.exceptions import l3 as l3_exc
from neutron_lib.exceptions import l3_ext_ha_mode as l3ha_exc
from neutron_lib.objects import exceptions
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from oslo_config import cfg
@ -84,6 +85,10 @@ class L3HATestFramework(testlib_api.SqlTestCase):
self.agent1 = helpers.register_l3_agent()
self.agent2 = helpers.register_l3_agent(
'host_2', constants.L3_AGENT_MODE_DVR_SNAT)
# Extend network HA extension.
rname = network_ha.COLLECTION_NAME
attributes.RESOURCES[rname].update(
network_ha.RESOURCE_ATTRIBUTE_MAP[rname])
@property
def admin_ctx(self):
@ -630,7 +635,8 @@ class L3HATestCase(L3HATestFramework):
with mock.patch.object(l3_hamode, 'L3HARouterNetwork',
side_effect=ValueError):
self.assertRaises(ValueError, self.plugin._create_ha_network,
self.assertRaises(c_exc.CallbackFailure,
self.plugin._create_ha_network,
self.admin_ctx, _uuid())
networks_after = self.core_plugin.get_networks(self.admin_ctx)
@ -682,15 +688,24 @@ class L3HATestCase(L3HATestFramework):
self.admin_ctx, binding.port_id)
def test_create_ha_network_tenant_binding_raises_duplicate(self):
router = self._create_router()
network = self.plugin.get_ha_network(self.admin_ctx,
router['tenant_id'])
self.plugin._create_ha_network_tenant_binding(
self.admin_ctx, 't1', network['network_id'])
with testtools.ExpectedException(
exceptions.NeutronDbObjectDuplicateEntry):
# The router creation calls first the HA network creation and the
# HA network-tenant binding ("ha_router_networks" register)
project_id = uuidutils.generate_uuid()
self._create_router(tenant_id=project_id)
network = self.core_plugin.get_networks(self.admin_ctx)[0]
ha_network = self.plugin.get_ha_network(self.admin_ctx, project_id)
self.assertEqual(project_id, ha_network.project_id)
self.assertEqual(network['id'], ha_network.network_id)
with testtools.ExpectedException(l3_hamode_db.DuplicatedHANetwork):
network[network_ha.HA] = True
ctx = self.admin_ctx # That will create a new admin context
ctx.project_id = project_id
payload = events.DBEventPayload(
ctx, states=(network, ), resource_id=network['id'],
request_body=network)
self.plugin._create_ha_network_tenant_binding(
self.admin_ctx, 't1', network['network_id'])
mock.ANY, mock.ANY, mock.ANY, payload=payload)
def test_create_router_db_vr_id_allocation_goes_to_error(self):
for method in ('_ensure_vr_id',
@ -956,12 +971,15 @@ class L3HATestCase(L3HATestFramework):
class L3HAModeDbTestCase(L3HATestFramework):
def _create_network(self, plugin, ctx, name='net',
tenant_id='tenant1', external=False):
tenant_id='tenant1', external=False, ha=False):
network = {'network': {'name': name,
'shared': False,
'admin_state_up': True,
'tenant_id': tenant_id,
extnet_apidef.EXTERNAL: external}}
'project_id': tenant_id,
extnet_apidef.EXTERNAL: external,
network_ha.HA: ha,
}}
return plugin.create_network(ctx, network)['id']
def _create_subnet(self, plugin, ctx, network_id, cidr='10.0.0.0/8',
@ -1380,6 +1398,21 @@ class L3HAModeDbTestCase(L3HATestFramework):
router_ids=[router['id']])
self.assertEqual(self.agent2['host'], routers[0]['gw_port_host'])
def test__before_router_create_no_network(self):
project_id = 'project1'
ha_network = self.plugin.get_ha_network(self.admin_ctx, project_id)
self.assertIsNone(ha_network)
router = {'ha': True, 'project_id': project_id}
self.plugin._before_router_create(mock.ANY, self.admin_ctx, router)
ha_network = self.plugin.get_ha_network(self.admin_ctx, project_id)
self.assertEqual(project_id, ha_network.project_id)
# This second call ensures the method is idempotent.
self.plugin._before_router_create(mock.ANY, self.admin_ctx, router)
ha_network = self.plugin.get_ha_network(self.admin_ctx, project_id)
self.assertEqual(project_id, ha_network.project_id)
class L3HAUserTestCase(L3HATestFramework):

View File

@ -16,6 +16,8 @@
from unittest import mock
from neutron_lib.agent import topics
from neutron_lib.api import attributes
from neutron_lib.api.definitions import network_ha
from neutron_lib.api.definitions import port as port_def
from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import provider_net as pnet
@ -120,6 +122,10 @@ class TestL2PopulationRpcTestCase(test_plugin.Ml2PluginV2TestCase):
uptime = ('neutron.plugins.ml2.drivers.l2pop.db.get_agent_uptime')
uptime_patch = mock.patch(uptime, return_value=190)
uptime_patch.start()
# Extend network HA extension.
rname = network_ha.COLLECTION_NAME
attributes.RESOURCES[rname].update(
network_ha.RESOURCE_ATTRIBUTE_MAP[rname])
def _setup_l3(self):
notif_p = mock.patch.object(l3_hamode_db.L3_HA_NAT_db_mixin,

View File

@ -18,7 +18,9 @@ import contextlib
import datetime
from unittest import mock
from neutron_lib.api import attributes
from neutron_lib.api.definitions import l3_ext_ha_mode
from neutron_lib.api.definitions import network_ha
from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import router_availability_zone
from neutron_lib.callbacks import events
@ -1455,6 +1457,10 @@ class L3HATestCaseMixin(testlib_api.SqlTestCase,
self.mock_make_res = make_res.start()
commit_res = mock.patch.object(quota.QuotaEngine, 'commit_reservation')
self.mock_quota_commit_res = commit_res.start()
# Extend network HA extension.
rname = network_ha.COLLECTION_NAME
attributes.RESOURCES[rname].update(
network_ha.RESOURCE_ATTRIBUTE_MAP[rname])
@staticmethod
def get_router_l3_agent_binding(context, router_id, l3_agent_id=None,
@ -2113,6 +2119,10 @@ class L3AgentAZLeastRoutersSchedulerTestCase(L3HATestCaseMixin):
self.patch_notifier = mock.patch(
'neutron.notifiers.batch_notifier.BatchNotifier._notify')
self.patch_notifier.start()
# Extend network HA extension.
rname = network_ha.COLLECTION_NAME
attributes.RESOURCES[rname].update(
network_ha.RESOURCE_ATTRIBUTE_MAP[rname])
def _register_l3_agents(self):
self.agent1 = helpers.register_l3_agent(host='az1-host1', az='az1')

View File

@ -0,0 +1,8 @@
---
features:
- |
A new API extension ``network-ha`` has been added. This extension adds a
new field to the network API: "ha". This field is not visible and can be
passed only in POST (create) operations. That will define that this network
is a high availability (HA) network and will create, in the same database
transaction, a ``ha_router_networks`` register.

View File

@ -20,7 +20,7 @@ Jinja2>=2.10 # BSD License (3 clause)
keystonemiddleware>=5.1.0 # Apache-2.0
netaddr>=0.7.18 # BSD
netifaces>=0.10.4 # MIT
neutron-lib>=3.6.1 # Apache-2.0
neutron-lib>=3.7.0 # Apache-2.0
python-neutronclient>=7.8.0 # Apache-2.0
tenacity>=6.0.0 # Apache-2.0
SQLAlchemy>=1.4.23 # MIT