Merge "Add unique constraint for network segment ranges"
This commit is contained in:
@@ -18,7 +18,7 @@ from oslo_utils import timeutils
|
||||
from neutron.common import utils
|
||||
|
||||
|
||||
FIRST_WORKER_ID = 1
|
||||
FIRST_WORKER_ID = None
|
||||
|
||||
|
||||
def get_start_time(default=None, current_time=False):
|
||||
|
@@ -0,0 +1,111 @@
|
||||
# Copyright 2025 OpenStack Foundation
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
"""Add unique constraint to the network segment range
|
||||
|
||||
Revision ID: b1bca967e19d
|
||||
Revises: ad80a9f07c5c
|
||||
Create Date: 2025-04-08 11:28:47.791807
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'b1bca967e19d'
|
||||
down_revision = 'd553edeb540f'
|
||||
|
||||
network_segment_range_network_type = sa.Enum(
|
||||
'vlan', 'vxlan', 'gre', 'geneve',
|
||||
name='network_segment_range_network_type')
|
||||
|
||||
TABLE_NAME = 'network_segment_ranges'
|
||||
network_segment_range_table = sa.Table(
|
||||
TABLE_NAME, sa.MetaData(),
|
||||
sa.Column('id', sa.String(length=36), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('default', sa.Boolean(), nullable=False),
|
||||
sa.Column('shared', sa.Boolean(), nullable=False),
|
||||
sa.Column('project_id', sa.String(length=255), nullable=True),
|
||||
sa.Column('network_type', network_segment_range_network_type,
|
||||
nullable=False),
|
||||
sa.Column('physical_network', sa.String(length=64), nullable=False,
|
||||
server_default=''),
|
||||
sa.Column('minimum', sa.Integer(), nullable=True),
|
||||
sa.Column('maximum', sa.Integer(), nullable=True),
|
||||
sa.Column('standard_attr_id', sa.BigInteger(), nullable=False),
|
||||
)
|
||||
|
||||
|
||||
def upgrade():
|
||||
unique_name = 'uniq_network_segment_ranges'
|
||||
unique_columns = ['default',
|
||||
'network_type',
|
||||
'physical_network',
|
||||
'minimum',
|
||||
'maximum',
|
||||
]
|
||||
|
||||
inspect = sa.engine.reflection.Inspector.from_engine(op.get_bind())
|
||||
unique_constraints = inspect.get_unique_constraints(TABLE_NAME)
|
||||
for unique_constraint in unique_constraints:
|
||||
if unique_constraint['name'] == unique_name:
|
||||
# The unique constraint already exists.
|
||||
return
|
||||
|
||||
migrate_values()
|
||||
clear_duplicate_values()
|
||||
op.alter_column(TABLE_NAME, 'physical_network', nullable=False,
|
||||
server_default='', existing_type=sa.String(64))
|
||||
op.create_unique_constraint(
|
||||
columns=unique_columns,
|
||||
constraint_name=unique_name,
|
||||
table_name=TABLE_NAME)
|
||||
|
||||
|
||||
def clear_duplicate_values():
|
||||
session = sa.orm.Session(bind=op.get_bind())
|
||||
values = []
|
||||
for row in session.query(network_segment_range_table):
|
||||
id = row[0]
|
||||
item = {'default': row[2],
|
||||
'network_type': row[5],
|
||||
'physical_network': row[6],
|
||||
'minimum': row[7],
|
||||
'maximum': row[8]}
|
||||
if item not in values:
|
||||
values.append(item)
|
||||
else:
|
||||
session.execute(
|
||||
network_segment_range_table.delete().where(
|
||||
network_segment_range_table.c.id == id))
|
||||
session.commit()
|
||||
|
||||
|
||||
def migrate_values():
|
||||
session = sa.orm.Session(bind=op.get_bind())
|
||||
values = []
|
||||
for row in session.query(network_segment_range_table):
|
||||
values.append({'id': row[0],
|
||||
'physical_network': row[6]})
|
||||
for value in values:
|
||||
physical_network = value['physical_network'] or ''
|
||||
session.execute(
|
||||
network_segment_range_table.update().values(
|
||||
physical_network=physical_network
|
||||
).where(network_segment_range_table.c.id == value['id']))
|
||||
session.commit()
|
@@ -1 +1 @@
|
||||
d553edeb540f
|
||||
b1bca967e19d
|
||||
|
@@ -52,7 +52,8 @@ class NetworkSegmentRange(standard_attr.HasStandardAttributes,
|
||||
nullable=False)
|
||||
|
||||
# network segment range physical network, only applicable for VLAN.
|
||||
physical_network = sa.Column(sa.String(64))
|
||||
physical_network = sa.Column(sa.String(64), nullable=False,
|
||||
server_default='')
|
||||
|
||||
# minimum segmentation id value
|
||||
minimum = sa.Column(sa.Integer)
|
||||
@@ -65,11 +66,18 @@ class NetworkSegmentRange(standard_attr.HasStandardAttributes,
|
||||
range_apidef.COLLECTION_NAME: range_apidef.RESOURCE_NAME}
|
||||
tag_support = True
|
||||
|
||||
__table_args__ = (
|
||||
sa.UniqueConstraint('default', 'network_type',
|
||||
'physical_network',
|
||||
'minimum', 'maximum',
|
||||
name='uniq_network_segment_ranges'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.project_id = None if self.shared else kwargs['project_id']
|
||||
is_vlan = self.network_type == constants.TYPE_VLAN
|
||||
self.physical_network = kwargs['physical_network'] if is_vlan else None
|
||||
self.physical_network = kwargs['physical_network'] if is_vlan else ''
|
||||
|
||||
def __repr__(self):
|
||||
return "<NetworkSegmentRange({},{},{},{},{},{} - {},{})>".format(
|
||||
|
@@ -109,6 +109,10 @@ def get_segment_by_id(context, segment_id):
|
||||
def get_dynamic_segment(context, network_id, physical_network=None,
|
||||
segmentation_id=None):
|
||||
"""Return a dynamic segment for the filters provided if one exists."""
|
||||
# Network segments have physical_network=None in tunnelled networks, unlike
|
||||
# network segment ranges, that have an empty string in order to force the
|
||||
# database constraint.
|
||||
physical_network = physical_network or None
|
||||
with db_api.CONTEXT_READER.using(context):
|
||||
filters = {
|
||||
'network_id': network_id,
|
||||
@@ -142,6 +146,10 @@ def delete_network_segment(context, segment_id):
|
||||
def network_segments_exist_in_range(context, network_type, physical_network,
|
||||
segment_range=None):
|
||||
"""Check whether one or more network segments exist in a range."""
|
||||
# Network segments have physical_network=None in tunnelled networks, unlike
|
||||
# network segment ranges, that have an empty string in order to force the
|
||||
# database constraint.
|
||||
physical_network = physical_network or None
|
||||
with db_api.CONTEXT_READER.using(context):
|
||||
filters = {
|
||||
'network_type': network_type,
|
||||
@@ -163,6 +171,10 @@ def min_max_actual_segments_in_range(context, network_type, physical_network,
|
||||
"""Return the minimum and maximum segmentation IDs used in a network
|
||||
segment range
|
||||
"""
|
||||
# Network segments have physical_network=None in tunnelled networks, unlike
|
||||
# network segment ranges, that have an empty string in order to force the
|
||||
# database constraint.
|
||||
physical_network = physical_network or None
|
||||
with db_api.CONTEXT_READER.using(context):
|
||||
filters = {
|
||||
'network_type': network_type,
|
||||
|
@@ -54,7 +54,9 @@ models_map = {
|
||||
@base.NeutronObjectRegistry.register
|
||||
class NetworkSegmentRange(base.NeutronDbObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
# Version 1.1: Add unique constraint and make 'physical_network' not
|
||||
# nullable
|
||||
VERSION = '1.1'
|
||||
|
||||
db_model = range_model.NetworkSegmentRange
|
||||
|
||||
@@ -68,7 +70,7 @@ class NetworkSegmentRange(base.NeutronDbObject):
|
||||
'project_id': obj_fields.StringField(nullable=True),
|
||||
'network_type': common_types.NetworkSegmentRangeNetworkTypeEnumField(
|
||||
nullable=False),
|
||||
'physical_network': obj_fields.StringField(nullable=True),
|
||||
'physical_network': obj_fields.StringField(nullable=False),
|
||||
'minimum': obj_fields.IntegerField(nullable=True),
|
||||
'maximum': obj_fields.IntegerField(nullable=True)
|
||||
}
|
||||
@@ -131,6 +133,7 @@ class NetworkSegmentRange(base.NeutronDbObject):
|
||||
return [segmentation_id for (segmentation_id,) in alloc_available]
|
||||
|
||||
def _get_used_allocation_mapping(self):
|
||||
phys_net = self.physical_network or None
|
||||
with self.db_context_reader(self.obj_context):
|
||||
query = self.obj_context.session.query(
|
||||
segments_model.NetworkSegment.segmentation_id,
|
||||
@@ -138,8 +141,7 @@ class NetworkSegmentRange(base.NeutronDbObject):
|
||||
alloc_used = (query.filter(and_(
|
||||
segments_model.NetworkSegment.network_type ==
|
||||
self.network_type,
|
||||
segments_model.NetworkSegment.physical_network ==
|
||||
self.physical_network,
|
||||
segments_model.NetworkSegment.physical_network == phys_net,
|
||||
segments_model.NetworkSegment.segmentation_id >= self.minimum,
|
||||
segments_model.NetworkSegment.segmentation_id <= self.maximum))
|
||||
.filter(segments_model.NetworkSegment.network_id ==
|
||||
@@ -253,6 +255,7 @@ class NetworkSegmentRange(base.NeutronDbObject):
|
||||
@classmethod
|
||||
def new_default(cls, context, network_type, physical_network,
|
||||
minimum, maximum, start_time):
|
||||
physical_network = physical_network or ''
|
||||
model = models_map.get(network_type)
|
||||
if not model:
|
||||
msg = (_("network_type '%s' unknown for getting allocation "
|
||||
|
@@ -22,6 +22,7 @@ from neutron_lib import constants as p_const
|
||||
from neutron_lib import context
|
||||
from neutron_lib.db import api as db_api
|
||||
from neutron_lib import exceptions as exc
|
||||
from neutron_lib.objects import exceptions as o_exc
|
||||
from neutron_lib.plugins import constants as plugin_constants
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_lib.plugins.ml2 import api
|
||||
@@ -144,7 +145,6 @@ class _TunnelTypeDriverBase(helpers.SegmentTypeDriver, metaclass=abc.ABCMeta):
|
||||
LOG.info("%(type)s ID ranges: %(range)s",
|
||||
{'type': self.get_type(), 'range': current_range})
|
||||
|
||||
@db_api.retry_db_errors
|
||||
def _populate_new_default_network_segment_ranges(self, ctx, start_time):
|
||||
for tun_min, tun_max in self._tunnel_ranges:
|
||||
range_obj.NetworkSegmentRange.new_default(
|
||||
@@ -165,18 +165,22 @@ class _TunnelTypeDriverBase(helpers.SegmentTypeDriver, metaclass=abc.ABCMeta):
|
||||
@db_api.retry_db_errors
|
||||
def initialize_network_segment_range_support(self, start_time):
|
||||
admin_context = context.get_admin_context()
|
||||
with db_api.CONTEXT_WRITER.using(admin_context):
|
||||
self._delete_expired_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
self._populate_new_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
# Override self.tunnel_ranges with the network segment range
|
||||
# information from DB and then do a sync_allocations since the
|
||||
# segment range service plugin has not yet been loaded at this
|
||||
# initialization time.
|
||||
self._tunnel_ranges = self._get_network_segment_ranges_from_db(
|
||||
ctx=admin_context)
|
||||
self._sync_allocations(ctx=admin_context)
|
||||
try:
|
||||
with db_api.CONTEXT_WRITER.using(admin_context):
|
||||
self._delete_expired_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
self._populate_new_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
except o_exc.NeutronDbObjectDuplicateEntry:
|
||||
pass
|
||||
|
||||
# Override self.tunnel_ranges with the network segment range
|
||||
# information from DB and then do a sync_allocations since the
|
||||
# segment range service plugin has not yet been loaded at this
|
||||
# initialization time.
|
||||
self._tunnel_ranges = self._get_network_segment_ranges_from_db(
|
||||
ctx=admin_context)
|
||||
self._sync_allocations(ctx=admin_context)
|
||||
|
||||
def update_network_segment_range_allocations(self):
|
||||
self._sync_allocations()
|
||||
|
@@ -20,6 +20,7 @@ from neutron_lib import constants as p_const
|
||||
from neutron_lib import context
|
||||
from neutron_lib.db import api as db_api
|
||||
from neutron_lib import exceptions as exc
|
||||
from neutron_lib.objects import exceptions as o_exc
|
||||
from neutron_lib.plugins import constants as plugin_constants
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_lib.plugins.ml2 import api
|
||||
@@ -171,18 +172,22 @@ class VlanTypeDriver(helpers.SegmentTypeDriver):
|
||||
@db_api.retry_db_errors
|
||||
def initialize_network_segment_range_support(self, start_time):
|
||||
admin_context = context.get_admin_context()
|
||||
with db_api.CONTEXT_WRITER.using(admin_context):
|
||||
self._delete_expired_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
self._populate_new_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
# Override self._network_vlan_ranges with the network segment range
|
||||
# information from DB and then do a sync_allocations since the
|
||||
# segment range service plugin has not yet been loaded at this
|
||||
# initialization time.
|
||||
self._network_vlan_ranges = (
|
||||
self._get_network_segment_ranges_from_db(ctx=admin_context))
|
||||
self._sync_vlan_allocations(ctx=admin_context)
|
||||
try:
|
||||
with db_api.CONTEXT_WRITER.using(admin_context):
|
||||
self._delete_expired_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
self._populate_new_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
except o_exc.NeutronDbObjectDuplicateEntry:
|
||||
pass
|
||||
|
||||
# Override self._network_vlan_ranges with the network segment range
|
||||
# information from DB and then do a sync_allocations since the
|
||||
# segment range service plugin has not yet been loaded at this
|
||||
# initialization time.
|
||||
self._network_vlan_ranges = (
|
||||
self._get_network_segment_ranges_from_db(ctx=admin_context))
|
||||
self._sync_vlan_allocations(ctx=admin_context)
|
||||
|
||||
def update_network_segment_range_allocations(self):
|
||||
self._sync_vlan_allocations()
|
||||
|
@@ -165,7 +165,7 @@ class NetworkSegmentRangePlugin(ext_range.NetworkSegmentRangePluginBase):
|
||||
network_type=range_data['network_type'],
|
||||
physical_network=(range_data['physical_network']
|
||||
if range_data['network_type'] ==
|
||||
const.TYPE_VLAN else None),
|
||||
const.TYPE_VLAN else ''),
|
||||
minimum=range_data['minimum'],
|
||||
maximum=range_data['maximum'])
|
||||
)
|
||||
|
@@ -0,0 +1,106 @@
|
||||
# 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.
|
||||
|
||||
import random
|
||||
|
||||
from oslo_db.sqlalchemy import utils as db_utils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.tests.functional.db import test_migrations
|
||||
|
||||
|
||||
class NetworkSegmentRangesUniqueUpgrade(test_migrations.TestWalkMigrations):
|
||||
"""Validates migration that adds unique constraints for
|
||||
network segment ranges.
|
||||
"""
|
||||
|
||||
_standard_attribute_id = 0
|
||||
|
||||
def _gen_attr_id(self, type):
|
||||
self._standard_attribute_id = random.randint(100000, 2000000)
|
||||
standardattributes = db_utils.get_table(
|
||||
self.engine, 'standardattributes')
|
||||
with self.engine.connect() as conn, conn.begin():
|
||||
conn.execute(standardattributes.insert().values({
|
||||
'id': self._standard_attribute_id,
|
||||
'resource_type': type}))
|
||||
return self._standard_attribute_id
|
||||
|
||||
def _create_network_segment_ranges(self, data):
|
||||
network_segment_ranges = db_utils.get_table(
|
||||
self.engine, 'network_segment_ranges')
|
||||
with self.engine.connect() as conn, conn.begin():
|
||||
for item in data:
|
||||
range_dict = {
|
||||
'id': uuidutils.generate_uuid(),
|
||||
'standard_attr_id': self._gen_attr_id(
|
||||
'network_segment_ranges'),
|
||||
}
|
||||
range_dict.update(**item)
|
||||
conn.execute(
|
||||
network_segment_ranges.insert().values(range_dict))
|
||||
|
||||
def _pre_upgrade_b1bca967e19d(self, engine):
|
||||
duplicate_data = [
|
||||
{
|
||||
'name': '',
|
||||
'default': True,
|
||||
'shared': True,
|
||||
'network_type': 'vlan',
|
||||
'physical_network': 'default',
|
||||
'minimum': 100,
|
||||
'maximum': 200
|
||||
},
|
||||
{
|
||||
'name': '',
|
||||
'default': True,
|
||||
'shared': True,
|
||||
'network_type': 'vlan',
|
||||
'physical_network': 'default',
|
||||
'minimum': 100,
|
||||
'maximum': 200
|
||||
},
|
||||
{
|
||||
'name': '',
|
||||
'default': True,
|
||||
'shared': True,
|
||||
'network_type': 'vxlan',
|
||||
'minimum': 1000,
|
||||
'maximum': 2000
|
||||
},
|
||||
{
|
||||
'name': '',
|
||||
'default': True,
|
||||
'shared': True,
|
||||
'network_type': 'vxlan',
|
||||
'minimum': 1000,
|
||||
'maximum': 2000
|
||||
},
|
||||
]
|
||||
self._create_network_segment_ranges(duplicate_data)
|
||||
# Ensure there are two duplicate ranges data
|
||||
range_table = db_utils.get_table(self.engine, 'network_segment_ranges')
|
||||
with self.engine.connect() as conn, conn.begin():
|
||||
rows = conn.execute(range_table.select()).fetchall()
|
||||
self.assertEqual(4, len(rows))
|
||||
return True
|
||||
|
||||
def _check_b1bca967e19d(self, engine, data):
|
||||
range_table = db_utils.get_table(self.engine, 'network_segment_ranges')
|
||||
# check duplicate data is deleted
|
||||
with self.engine.connect() as conn, conn.begin():
|
||||
vlan = conn.execute(range_table.select().where(
|
||||
range_table.c.network_type == 'vlan')).fetchall()
|
||||
self.assertEqual(1, len(vlan))
|
||||
vxlan = conn.execute(range_table.select().where(
|
||||
range_table.c.network_type == 'vxlan')).fetchall()
|
||||
self.assertEqual(1, len(vxlan))
|
@@ -14,12 +14,11 @@
|
||||
# under the License.
|
||||
|
||||
from concurrent import futures
|
||||
import random
|
||||
import time
|
||||
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import context
|
||||
from neutron_lib.db import api as db_api
|
||||
from neutron_lib.objects import exceptions as o_exc
|
||||
from oslo_config import cfg
|
||||
|
||||
from neutron.conf import common as common_config
|
||||
@@ -37,15 +36,18 @@ def _initialize_network_segment_range_support(type_driver, start_time):
|
||||
# creates the new ones. It also adds an extra second before closing the
|
||||
# DB transaction.
|
||||
admin_context = context.get_admin_context()
|
||||
with db_api.CONTEXT_WRITER.using(admin_context):
|
||||
time.sleep(random.randrange(1000) / 1000)
|
||||
type_driver._delete_expired_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
type_driver._populate_new_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
try:
|
||||
with db_api.CONTEXT_WRITER.using(admin_context):
|
||||
type_driver._delete_expired_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
type_driver._populate_new_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
except o_exc.NeutronDbObjectDuplicateEntry:
|
||||
pass
|
||||
|
||||
|
||||
class TunnelTypeDriverBaseTestCase(testlib_api.SqlTestCase):
|
||||
class TunnelTypeDriverBaseTestCase(testlib_api.MySQLTestCaseMixin,
|
||||
testlib_api.SqlTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
cfg.CONF.register_opts(common_config.core_opts)
|
||||
@@ -62,32 +64,45 @@ class TunnelTypeDriverBaseTestCase(testlib_api.SqlTestCase):
|
||||
self.type_driver = type_geneve.GeneveTypeDriver()
|
||||
self.type_driver.initialize()
|
||||
|
||||
def _check_sranges(self, sranges):
|
||||
self.assertEqual(1, len(sranges))
|
||||
self.assertEqual(self.net_type, sranges[0].network_type)
|
||||
self.assertEqual(self.min, sranges[0].minimum)
|
||||
self.assertEqual(self.max, sranges[0].maximum)
|
||||
self.assertEqual([(self.min, self.max)],
|
||||
self.type_driver._tunnel_ranges)
|
||||
|
||||
def test_initialize_network_segment_range_support(self):
|
||||
# Execute the initialization several times with different start times.
|
||||
for start_time in range(3):
|
||||
self.type_driver.initialize_network_segment_range_support(
|
||||
start_time)
|
||||
sranges = range_obj.NetworkSegmentRange.get_objects(self.admin_ctx)
|
||||
self.assertEqual(1, len(sranges))
|
||||
self.assertEqual(self.net_type, sranges[0].network_type)
|
||||
self.assertEqual(self.min, sranges[0].minimum)
|
||||
self.assertEqual(self.max, sranges[0].maximum)
|
||||
self.assertEqual([(self.min, self.max)],
|
||||
self.type_driver._tunnel_ranges)
|
||||
self._check_sranges(sranges)
|
||||
|
||||
def test_initialize_network_segment_range_support_parallel_execution(self):
|
||||
def _test_initialize_nsrange(self, same_init_time=True):
|
||||
max_workers = 3
|
||||
_futures = []
|
||||
with futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
for idx in range(max_workers):
|
||||
if same_init_time:
|
||||
# All workers are started at the same init time.
|
||||
_futures.append(executor.submit(
|
||||
_initialize_network_segment_range_support,
|
||||
self.type_driver, idx))
|
||||
self.type_driver, 0))
|
||||
else:
|
||||
# All workers have different init times.
|
||||
for idx in range(max_workers):
|
||||
_futures.append(executor.submit(
|
||||
_initialize_network_segment_range_support,
|
||||
self.type_driver, idx))
|
||||
for _future in _futures:
|
||||
_future.result()
|
||||
|
||||
sranges = range_obj.NetworkSegmentRange.get_objects(self.admin_ctx)
|
||||
self.assertEqual(1, len(sranges))
|
||||
self.assertEqual(self.net_type, sranges[0].network_type)
|
||||
self.assertEqual(self.min, sranges[0].minimum)
|
||||
self.assertEqual(self.max, sranges[0].maximum)
|
||||
self._check_sranges(sranges)
|
||||
|
||||
def test_initialize_nsrange_support_parallel_exec_same_init_time(self):
|
||||
self._test_initialize_nsrange(same_init_time=True)
|
||||
|
||||
def test_initialize_nsrange_support_parallel_exec_diff_init_time(self):
|
||||
self._test_initialize_nsrange(same_init_time=False)
|
||||
|
118
neutron/tests/functional/plugins/ml2/drivers/test_type_vlan.py
Normal file
118
neutron/tests/functional/plugins/ml2/drivers/test_type_vlan.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# Copyright 2025 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 concurrent import futures
|
||||
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import context
|
||||
from neutron_lib.db import api as db_api
|
||||
from neutron_lib.objects import exceptions as o_exc
|
||||
from oslo_config import cfg
|
||||
|
||||
from neutron.conf import common as common_config
|
||||
from neutron.conf.plugins.ml2 import config as ml2_config
|
||||
from neutron.conf.plugins.ml2.drivers import driver_type as driver_type_config
|
||||
from neutron.objects import network_segment_range as range_obj
|
||||
from neutron.plugins.ml2.drivers import type_vlan
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
|
||||
def _initialize_network_segment_range_support(type_driver, start_time):
|
||||
# This method is similar to
|
||||
# ``VlanTypeDriverBase.initialize_network_segment_range_support``.
|
||||
# The method first deletes the existing default network ranges and then
|
||||
# creates the new ones. It also adds an extra second before closing the
|
||||
# DB transaction.
|
||||
admin_context = context.get_admin_context()
|
||||
try:
|
||||
with db_api.CONTEXT_WRITER.using(admin_context):
|
||||
type_driver._delete_expired_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
type_driver._populate_new_default_network_segment_ranges(
|
||||
admin_context, start_time)
|
||||
except o_exc.NeutronDbObjectDuplicateEntry:
|
||||
pass
|
||||
|
||||
|
||||
class VlanTypeDriverBaseTestCase(testlib_api.MySQLTestCaseMixin,
|
||||
testlib_api.SqlTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
cfg.CONF.register_opts(common_config.core_opts)
|
||||
ml2_config.register_ml2_plugin_opts()
|
||||
driver_type_config.register_ml2_drivers_vlan_opts()
|
||||
ml2_config.cfg.CONF.set_override(
|
||||
'service_plugins', 'network_segment_range')
|
||||
self.min = 1001
|
||||
self.max = 1020
|
||||
self.net_type = constants.TYPE_VLAN
|
||||
self.ranges = [f'phys1:{self.min}:{self.max}',
|
||||
f'phys2:{self.min}:{self.max}',
|
||||
f'phys3:{self.min}:{self.max}',
|
||||
]
|
||||
ml2_config.cfg.CONF.set_override(
|
||||
'network_vlan_ranges', self.ranges, group='ml2_type_vlan')
|
||||
self.admin_ctx = context.get_admin_context()
|
||||
self.type_driver = type_vlan.VlanTypeDriver()
|
||||
self.type_driver.initialize()
|
||||
|
||||
def _check_sranges(self, sranges):
|
||||
self.assertEqual(len(self.ranges), len(sranges))
|
||||
for _srange in sranges:
|
||||
self.assertEqual(self.net_type, _srange.network_type)
|
||||
self.assertEqual(self.min, _srange.minimum)
|
||||
self.assertEqual(self.max, _srange.maximum)
|
||||
self.assertIn(_srange.physical_network,
|
||||
('phys1', 'phys2', 'phys3'))
|
||||
|
||||
self.assertEqual({'phys1': [(self.min, self.max)],
|
||||
'phys2': [(self.min, self.max)],
|
||||
'phys3': [(self.min, self.max)]},
|
||||
self.type_driver._network_vlan_ranges)
|
||||
|
||||
def test_initialize_network_segment_range_support(self):
|
||||
# Execute the initialization several times with different start times.
|
||||
for start_time in range(3):
|
||||
self.type_driver.initialize_network_segment_range_support(
|
||||
start_time)
|
||||
sranges = range_obj.NetworkSegmentRange.get_objects(self.admin_ctx)
|
||||
self._check_sranges(sranges)
|
||||
|
||||
def _test_initialize_nsrange(self, same_init_time=True):
|
||||
max_workers = 3
|
||||
_futures = []
|
||||
with futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
if same_init_time:
|
||||
# All workers are started at the same init time.
|
||||
_futures.append(executor.submit(
|
||||
_initialize_network_segment_range_support,
|
||||
self.type_driver, 0))
|
||||
else:
|
||||
# All workers have different init times.
|
||||
for idx in range(max_workers):
|
||||
_futures.append(executor.submit(
|
||||
_initialize_network_segment_range_support,
|
||||
self.type_driver, idx))
|
||||
for _future in _futures:
|
||||
_future.result()
|
||||
|
||||
sranges = range_obj.NetworkSegmentRange.get_objects(self.admin_ctx)
|
||||
self._check_sranges(sranges)
|
||||
|
||||
def test__initialize_nsrange_support_parallel_exec_same_init_time(self):
|
||||
self._test_initialize_nsrange(same_init_time=True)
|
||||
|
||||
def test_initialize_nsrange_support_parallel_exec_diff_init_time(self):
|
||||
self._test_initialize_nsrange(same_init_time=False)
|
@@ -171,10 +171,10 @@ class TestNetworkSegmentRange(NetworkSegmentRangeTestBase):
|
||||
expected_range = {'shared': True,
|
||||
'project_id': None,
|
||||
'network_type': constants.TYPE_VXLAN,
|
||||
'physical_network': None}
|
||||
'physical_network': ''}
|
||||
self._test_create_network_segment_range(
|
||||
network_type=constants.TYPE_VXLAN,
|
||||
physical_network=None,
|
||||
physical_network='',
|
||||
expected=expected_range)
|
||||
|
||||
def test_create_network_segment_range_tenant_specific(self):
|
||||
|
@@ -18,6 +18,7 @@ from unittest import mock
|
||||
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import exceptions as n_exc
|
||||
from neutron_lib.objects import exceptions as obj_exc
|
||||
from neutron_lib.utils import helpers
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
@@ -410,23 +411,17 @@ class NetworkSegmentRangeDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
||||
num_ranges = 5
|
||||
for network_type in network_segment_range.models_map.keys():
|
||||
for idx in range(num_ranges):
|
||||
obj = self._create_network_segment_range(
|
||||
1, 10, network_type=network_type, default=True,
|
||||
shared=True, start_time=start_time - idx)
|
||||
obj.create()
|
||||
ranges = network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context, default=True, shared=True,
|
||||
network_type=network_type)
|
||||
self.assertEqual(num_ranges, len(ranges))
|
||||
|
||||
network_segment_range.NetworkSegmentRange.\
|
||||
delete_expired_default_network_segment_ranges(
|
||||
self.context, network_type, start_time)
|
||||
# NOTE(ralonsoh): there should be just one that has the same
|
||||
# "created_at" value as "start_time".
|
||||
try:
|
||||
obj = self._create_network_segment_range(
|
||||
1, 10, network_type=network_type, default=True,
|
||||
shared=True, start_time=start_time - idx)
|
||||
obj.create()
|
||||
except obj_exc.NeutronDbObjectDuplicateEntry:
|
||||
pass
|
||||
ranges = network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context, default=True, shared=True,
|
||||
network_type=network_type)
|
||||
# No duplicated entry in DB at all
|
||||
self.assertEqual(1, len(ranges))
|
||||
|
||||
def test_new_default(self):
|
||||
|
@@ -71,7 +71,7 @@ object_data = {
|
||||
'NetworkPortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
|
||||
'NetworkRBAC': '1.3-be82ed54376b85ee4f963d479ac48c91',
|
||||
'NetworkSegment': '1.0-57b7f2960971e3b95ded20cbc59244a8',
|
||||
'NetworkSegmentRange': '1.0-bdec1fffc9058ea676089b1f2f2b3cf3',
|
||||
'NetworkSegmentRange': '1.1-ed71c4bd2d3f06c3da5b4a1b3069b69f',
|
||||
'NetworkSubnetLock': '1.0-140de39d4b86ae346dc3d70b885bea53',
|
||||
'Port': '1.10-ae84f686bfc3deb4017495134da6ef04',
|
||||
'PortHardwareOffloadType': '1.0-5f424d02b144fd1832ac3e6b03662674',
|
||||
|
@@ -496,7 +496,7 @@ class TunnelTypeNetworkSegmentRangeTestMixin:
|
||||
self.assertIsNone(network_segment_range.project_id)
|
||||
self.assertEqual(self.driver.get_type(),
|
||||
network_segment_range.network_type)
|
||||
self.assertIsNone(network_segment_range.physical_network)
|
||||
self.assertEqual('', network_segment_range.physical_network)
|
||||
self.assertEqual(TUN_MIN, network_segment_range.minimum)
|
||||
self.assertEqual(TUN_MAX, network_segment_range.maximum)
|
||||
|
||||
@@ -508,3 +508,15 @@ class TunnelTypeNetworkSegmentRangeTestMixin:
|
||||
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context, network_type=self.driver.get_type())
|
||||
self.assertEqual(0, len(ret))
|
||||
|
||||
def test_try_to_create_duplicate_network_segment_ranges(self):
|
||||
self.driver.initialize_network_segment_range_support(self.start_time)
|
||||
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context)
|
||||
self.assertEqual(1, len(ret))
|
||||
|
||||
self.driver._populate_new_default_network_segment_ranges(
|
||||
self.context, self.start_time)
|
||||
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context)
|
||||
self.assertEqual(1, len(ret))
|
||||
|
@@ -410,3 +410,15 @@ class VlanTypeTestWithNetworkSegmentRange(testlib_api.SqlTestCase):
|
||||
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context, network_type=self.driver.get_type())
|
||||
self.assertEqual(0, len(ret))
|
||||
|
||||
def test_try_to_create_duplicate_network_segment_ranges(self):
|
||||
self.driver.initialize_network_segment_range_support(self.start_time)
|
||||
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context)
|
||||
self.assertEqual(2, len(ret))
|
||||
|
||||
self.driver._populate_new_default_network_segment_ranges(
|
||||
self.context, self.start_time)
|
||||
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context)
|
||||
self.assertEqual(2, len(ret))
|
||||
|
Reference in New Issue
Block a user