Merge "Use the new network HA parameter"
This commit is contained in:
commit
f91429100b
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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'):
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue