Add unique constraint for network segment ranges

The network segment range initialization is performed
in each Neutron API worker during the initialization
transient period which may create multiple instances.
This patch add uniques constraints to avoid it.

Depends-On: https://review.opendev.org/c/openstack/neutron-tempest-plugin/+/961361

Closes-Bug: #2106463
Related-Bug: #2122107

Signed-off-by: LIU Yulong <i@liuyulong.me>
Co-authored-by: Rodolfo Alonso Hernandez <ralonsoh@redhat.com>
Change-Id: I061b904506af3c2187b3d057f91de7c9a547e712
This commit is contained in:
LIU Yulong
2025-04-09 09:29:25 +08:00
committed by Rodolfo Alonso Hernandez
parent aab1d698dc
commit 4f1e351169
18 changed files with 475 additions and 74 deletions

View File

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

View File

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

View File

@@ -1 +1 @@
d553edeb540f
b1bca967e19d

View File

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

View File

@@ -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,

View File

@@ -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 "

View File

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

View File

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

View File

@@ -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'])
)

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -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',

View File

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

View File

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