Skip to content

Commit fd57d6a

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Initialize the network segment ranges only in first WSGI worker" into stable/2025.1
2 parents aedb60d + f873d55 commit fd57d6a

File tree

8 files changed

+167
-57
lines changed

8 files changed

+167
-57
lines changed

neutron/common/wsgi_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
from neutron.common import utils
2121

2222

23+
FIRST_WORKER_ID = 1
24+
25+
2326
def get_start_time(default=None, current_time=False):
2427
"""Return the 'start-time=%t' config varible in the WSGI config
2528

neutron/plugins/ml2/drivers/helpers.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
import functools
1717

18-
from neutron_lib import context
1918
from neutron_lib.db import api as db_api
2019
from neutron_lib import exceptions
2120
from neutron_lib.plugins import constants as plugin_constants
@@ -165,8 +164,7 @@ def allocate_partially_specified_segment(self, context, **filters):
165164
context.elevated()):
166165
LOG.debug(' - %s', srange)
167166

168-
@db_api.retry_db_errors
169-
def _delete_expired_default_network_segment_ranges(self, start_time):
170-
ns_range.NetworkSegmentRange.\
171-
delete_expired_default_network_segment_ranges(
172-
context.get_admin_context(), self.get_type(), start_time)
167+
def _delete_expired_default_network_segment_ranges(self, ctx, start_time):
168+
(ns_range.NetworkSegmentRange.
169+
delete_expired_default_network_segment_ranges(
170+
ctx, self.get_type(), start_time))

neutron/plugins/ml2/drivers/type_tunnel.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def _initialize(self, raw_tunnel_ranges):
127127
# allocation during driver initialization, instead of using the
128128
# directory.get_plugin() method - the normal way used elsewhere to
129129
# check if a plugin is loaded.
130-
self.sync_allocations()
130+
self._sync_allocations()
131131

132132
def _parse_tunnel_ranges(self, tunnel_ranges, current_range):
133133
for entry in tunnel_ranges:
@@ -145,17 +145,15 @@ def _parse_tunnel_ranges(self, tunnel_ranges, current_range):
145145
{'type': self.get_type(), 'range': current_range})
146146

147147
@db_api.retry_db_errors
148-
def _populate_new_default_network_segment_ranges(self, start_time):
149-
ctx = context.get_admin_context()
150-
with db_api.CONTEXT_WRITER.using(ctx):
151-
for tun_min, tun_max in self.tunnel_ranges:
152-
range_obj.NetworkSegmentRange.new_default(
153-
ctx, self.get_type(), None, tun_min, tun_max, start_time)
148+
def _populate_new_default_network_segment_ranges(self, ctx, start_time):
149+
for tun_min, tun_max in self.tunnel_ranges:
150+
range_obj.NetworkSegmentRange.new_default(
151+
ctx, self.get_type(), None, tun_min, tun_max, start_time)
154152

155153
@db_api.retry_db_errors
156-
def _get_network_segment_ranges_from_db(self):
154+
def _get_network_segment_ranges_from_db(self, ctx=None):
157155
ranges = []
158-
ctx = context.get_admin_context()
156+
ctx = ctx or context.get_admin_context()
159157
with db_api.CONTEXT_READER.using(ctx):
160158
range_objs = (range_obj.NetworkSegmentRange.get_objects(
161159
ctx, network_type=self.get_type()))
@@ -164,21 +162,27 @@ def _get_network_segment_ranges_from_db(self):
164162

165163
return ranges
166164

165+
@db_api.retry_db_errors
167166
def initialize_network_segment_range_support(self, start_time):
168-
self._delete_expired_default_network_segment_ranges(start_time)
169-
self._populate_new_default_network_segment_ranges(start_time)
170-
# Override self.tunnel_ranges with the network segment range
171-
# information from DB and then do a sync_allocations since the
172-
# segment range service plugin has not yet been loaded at this
173-
# initialization time.
174-
self.tunnel_ranges = self._get_network_segment_ranges_from_db()
175-
self.sync_allocations()
167+
admin_context = context.get_admin_context()
168+
with db_api.CONTEXT_WRITER.using(admin_context):
169+
self._delete_expired_default_network_segment_ranges(
170+
admin_context, start_time)
171+
self._populate_new_default_network_segment_ranges(
172+
admin_context, start_time)
173+
# Override self.tunnel_ranges with the network segment range
174+
# information from DB and then do a sync_allocations since the
175+
# segment range service plugin has not yet been loaded at this
176+
# initialization time.
177+
self.tunnel_ranges = self._get_network_segment_ranges_from_db(
178+
ctx=admin_context)
179+
self._sync_allocations(ctx=admin_context)
176180

177181
def update_network_segment_range_allocations(self):
178-
self.sync_allocations()
182+
self._sync_allocations()
179183

180184
@db_api.retry_db_errors
181-
def sync_allocations(self):
185+
def _sync_allocations(self, ctx=None):
182186
# determine current configured allocatable tunnel ids
183187
tunnel_ids = set()
184188
ranges = self.get_network_segment_ranges()
@@ -187,7 +191,7 @@ def sync_allocations(self):
187191

188192
tunnel_id_getter = operator.attrgetter(self.segmentation_key)
189193
tunnel_col = getattr(self.model, self.segmentation_key)
190-
ctx = context.get_admin_context()
194+
ctx = ctx or context.get_admin_context()
191195
with db_api.CONTEXT_WRITER.using(ctx):
192196
# Check if the allocations are updated: if the total number of
193197
# allocations for this tunnel type matches the allocations of the

neutron/plugins/ml2/drivers/type_vlan.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,13 @@ def __init__(self):
5656
self.model_segmentation_id = vlan_alloc_model.VlanAllocation.vlan_id
5757
self._parse_network_vlan_ranges()
5858

59-
@db_api.retry_db_errors
60-
def _populate_new_default_network_segment_ranges(self, start_time):
61-
ctx = context.get_admin_context()
62-
with db_api.CONTEXT_WRITER.using(ctx):
63-
for (physical_network, vlan_ranges) in (
64-
self.network_vlan_ranges.items()):
65-
for vlan_min, vlan_max in vlan_ranges:
66-
range_obj.NetworkSegmentRange.new_default(
67-
ctx, self.get_type(), physical_network, vlan_min,
68-
vlan_max, start_time)
59+
def _populate_new_default_network_segment_ranges(self, ctx, start_time):
60+
for (physical_network, vlan_ranges) in (
61+
self.network_vlan_ranges.items()):
62+
for vlan_min, vlan_max in vlan_ranges:
63+
range_obj.NetworkSegmentRange.new_default(
64+
ctx, self.get_type(), physical_network, vlan_min,
65+
vlan_max, start_time)
6966

7067
def _parse_network_vlan_ranges(self):
7168
try:
@@ -78,8 +75,8 @@ def _parse_network_vlan_ranges(self):
7875
LOG.info("Network VLAN ranges: %s", self.network_vlan_ranges)
7976

8077
@db_api.retry_db_errors
81-
def _sync_vlan_allocations(self):
82-
ctx = context.get_admin_context()
78+
def _sync_vlan_allocations(self, ctx=None):
79+
ctx = ctx or context.get_admin_context()
8380
with db_api.CONTEXT_WRITER.using(ctx):
8481
# VLAN ranges per physical network:
8582
# {phy1: [(1, 10), (30, 50)], ...}
@@ -142,9 +139,9 @@ def _sync_vlan_allocations(self):
142139
vlan_ids)
143140

144141
@db_api.retry_db_errors
145-
def _get_network_segment_ranges_from_db(self):
142+
def _get_network_segment_ranges_from_db(self, ctx=None):
146143
ranges = {}
147-
ctx = context.get_admin_context()
144+
ctx = ctx or context.get_admin_context()
148145
with db_api.CONTEXT_READER.using(ctx):
149146
range_objs = (range_obj.NetworkSegmentRange.get_objects(
150147
ctx, network_type=self.get_type()))
@@ -171,15 +168,21 @@ def initialize(self):
171168
self._sync_vlan_allocations()
172169
LOG.info("VlanTypeDriver initialization complete")
173170

171+
@db_api.retry_db_errors
174172
def initialize_network_segment_range_support(self, start_time):
175-
self._delete_expired_default_network_segment_ranges(start_time)
176-
self._populate_new_default_network_segment_ranges(start_time)
177-
# Override self.network_vlan_ranges with the network segment range
178-
# information from DB and then do a sync_allocations since the
179-
# segment range service plugin has not yet been loaded at this
180-
# initialization time.
181-
self.network_vlan_ranges = self._get_network_segment_ranges_from_db()
182-
self._sync_vlan_allocations()
173+
admin_context = context.get_admin_context()
174+
with db_api.CONTEXT_WRITER.using(admin_context):
175+
self._delete_expired_default_network_segment_ranges(
176+
admin_context, start_time)
177+
self._populate_new_default_network_segment_ranges(
178+
admin_context, start_time)
179+
# Override self.network_vlan_ranges with the network segment range
180+
# information from DB and then do a sync_allocations since the
181+
# segment range service plugin has not yet been loaded at this
182+
# initialization time.
183+
self.network_vlan_ranges = (
184+
self._get_network_segment_ranges_from_db(ctx=admin_context))
185+
self._sync_vlan_allocations(ctx=admin_context)
183186

184187
def update_network_segment_range_allocations(self):
185188
self._sync_vlan_allocations()

neutron/plugins/ml2/managers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import stevedore
3333

3434
from neutron._i18n import _
35+
from neutron.common import wsgi_utils
3536
from neutron.conf.plugins.ml2 import config
3637
from neutron.db import segments_db
3738
from neutron.objects import ports
@@ -205,6 +206,9 @@ def initialize(self):
205206
driver.obj.initialize()
206207

207208
def initialize_network_segment_range_support(self, start_time):
209+
if wsgi_utils.get_api_worker_id() != wsgi_utils.FIRST_WORKER_ID:
210+
return
211+
208212
for network_type, driver in self.drivers.items():
209213
if network_type in constants.NETWORK_SEGMENT_RANGE_TYPES:
210214
LOG.info("Initializing driver network segment range support "
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright 2025 Red Hat Inc.
2+
# All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
# not use this file except in compliance with the License. You may obtain
6+
# a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
16+
from concurrent import futures
17+
import time
18+
19+
from neutron_lib import constants
20+
from neutron_lib import context
21+
from neutron_lib.db import api as db_api
22+
from oslo_config import cfg
23+
24+
from neutron.conf import common as common_config
25+
from neutron.conf.plugins.ml2 import config as ml2_config
26+
from neutron.conf.plugins.ml2.drivers import driver_type as driver_type_config
27+
from neutron.objects import network_segment_range as range_obj
28+
from neutron.plugins.ml2.drivers import type_geneve
29+
from neutron.tests.unit import testlib_api
30+
31+
32+
def _initialize_network_segment_range_support(type_driver, start_time):
33+
# This method is similar to
34+
# ``_TunnelTypeDriverBase.initialize_network_segment_range_support``.
35+
# The method first deletes the existing default network ranges and then
36+
# creates the new ones. It also adds an extra second before closing the
37+
# DB transaction.
38+
admin_context = context.get_admin_context()
39+
with db_api.CONTEXT_WRITER.using(admin_context):
40+
type_driver._delete_expired_default_network_segment_ranges(
41+
admin_context, start_time)
42+
type_driver._populate_new_default_network_segment_ranges(
43+
admin_context, start_time)
44+
time.sleep(1)
45+
46+
47+
class TunnelTypeDriverBaseTestCase(testlib_api.SqlTestCase):
48+
def setUp(self):
49+
super().setUp()
50+
cfg.CONF.register_opts(common_config.core_opts)
51+
ml2_config.register_ml2_plugin_opts()
52+
driver_type_config.register_ml2_drivers_geneve_opts()
53+
ml2_config.cfg.CONF.set_override(
54+
'service_plugins', 'network_segment_range')
55+
self.min = 1001
56+
self.max = 1020
57+
self.net_type = constants.TYPE_GENEVE
58+
ml2_config.cfg.CONF.set_override(
59+
'vni_ranges', f'{self.min}:{self.max}', group='ml2_type_geneve')
60+
self.admin_ctx = context.get_admin_context()
61+
self.type_driver = type_geneve.GeneveTypeDriver()
62+
self.type_driver.initialize()
63+
64+
def test_initialize_network_segment_range_support(self):
65+
# Execute the initialization several times with different start times.
66+
for start_time in range(3):
67+
self.type_driver.initialize_network_segment_range_support(
68+
start_time)
69+
sranges = range_obj.NetworkSegmentRange.get_objects(self.admin_ctx)
70+
self.assertEqual(1, len(sranges))
71+
self.assertEqual(self.net_type, sranges[0].network_type)
72+
self.assertEqual(self.min, sranges[0].minimum)
73+
self.assertEqual(self.max, sranges[0].maximum)
74+
self.assertEqual([(self.min, self.max)],
75+
self.type_driver.tunnel_ranges)
76+
77+
def test_initialize_network_segment_range_support_parallel_execution(self):
78+
max_workers = 3
79+
_futures = []
80+
with futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
81+
for idx in range(max_workers):
82+
_futures.append(executor.submit(
83+
_initialize_network_segment_range_support,
84+
self.type_driver, idx))
85+
for _future in _futures:
86+
_future.result()
87+
88+
sranges = range_obj.NetworkSegmentRange.get_objects(self.admin_ctx)
89+
self.assertEqual(1, len(sranges))
90+
self.assertEqual(self.net_type, sranges[0].network_type)
91+
self.assertEqual(self.min, sranges[0].minimum)
92+
self.assertEqual(self.max, sranges[0].maximum)

neutron/tests/unit/plugins/ml2/drivers/base_type_tunnel.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def setUp(self):
4949
super().setUp()
5050
self.driver = self.DRIVER_CLASS()
5151
self.driver.tunnel_ranges = TUNNEL_RANGES
52-
self.driver.sync_allocations()
52+
self.driver._sync_allocations()
5353
self.context = context.Context()
5454

5555
def test_tunnel_type(self):
@@ -84,7 +84,7 @@ def test_sync_tunnel_allocations(self):
8484
self.driver.get_allocation(self.context, (TUN_MAX + 1)))
8585

8686
self.driver.tunnel_ranges = UPDATED_TUNNEL_RANGES
87-
self.driver.sync_allocations()
87+
self.driver._sync_allocations()
8888

8989
self.assertIsNone(
9090
self.driver.get_allocation(self.context, (TUN_MIN + 5 - 1)))
@@ -108,7 +108,7 @@ def _test_sync_allocations_and_allocated(self, tunnel_id):
108108
self.driver.reserve_provider_segment(self.context, segment)
109109

110110
self.driver.tunnel_ranges = UPDATED_TUNNEL_RANGES
111-
self.driver.sync_allocations()
111+
self.driver._sync_allocations()
112112

113113
self.assertTrue(
114114
self.driver.get_allocation(self.context, tunnel_id).allocated)
@@ -127,7 +127,7 @@ def verify_no_chunk(iterable, chunk_size):
127127
return []
128128
with mock.patch.object(
129129
type_tunnel, 'chunks', side_effect=verify_no_chunk) as chunks:
130-
self.driver.sync_allocations()
130+
self.driver._sync_allocations()
131131
# No writing operation is done, fast exit: current allocations
132132
# already present.
133133
self.assertEqual(0, len(chunks.mock_calls))
@@ -295,7 +295,7 @@ def setUp(self):
295295
super().setUp()
296296
self.driver = self.DRIVER_CLASS()
297297
self.driver.tunnel_ranges = self.TUNNEL_MULTI_RANGES
298-
self.driver.sync_allocations()
298+
self.driver._sync_allocations()
299299
self.context = context.Context()
300300

301301
def test_release_segment(self):
@@ -486,7 +486,7 @@ def test__populate_new_default_network_segment_ranges(self):
486486
# one of the `service_plugins`
487487
self.driver._initialize(RAW_TUNNEL_RANGES)
488488
self.driver.initialize_network_segment_range_support(self.start_time)
489-
self.driver.sync_allocations()
489+
self.driver._sync_allocations()
490490
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
491491
self.context)
492492
self.assertEqual(1, len(ret))
@@ -502,9 +502,9 @@ def test__populate_new_default_network_segment_ranges(self):
502502

503503
def test__delete_expired_default_network_segment_ranges(self):
504504
self.driver.tunnel_ranges = TUNNEL_RANGES
505-
self.driver.sync_allocations()
505+
self.driver._sync_allocations()
506506
self.driver._delete_expired_default_network_segment_ranges(
507-
self.start_time)
507+
self.context, self.start_time)
508508
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
509509
self.context, network_type=self.driver.get_type())
510510
self.assertEqual(0, len(ret))

neutron/tests/unit/plugins/ml2/drivers/test_type_vlan.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@
2525
from oslo_config import cfg
2626
from testtools import matchers
2727

28+
from neutron.common import wsgi_utils
29+
from neutron.conf.plugins.ml2 import config as ml2_config
2830
from neutron.objects import network_segment_range as obj_network_segment_range
2931
from neutron.objects.plugins.ml2 import vlanallocation as vlan_alloc_obj
3032
from neutron.plugins.ml2.drivers import type_vlan
3133
from neutron.tests.unit import testlib_api
3234

35+
3336
PROVIDER_NET = 'phys_net1'
3437
TENANT_NET = 'phys_net2'
3538
UNCONFIGURED_NET = 'no_net'
@@ -361,6 +364,9 @@ def test_allocate_tenant_segment_in_order_of_config(self):
361364
class VlanTypeTestWithNetworkSegmentRange(testlib_api.SqlTestCase):
362365

363366
def setUp(self):
367+
ml2_config.register_ml2_plugin_opts()
368+
mock.patch.object(wsgi_utils, 'get_api_worker_id',
369+
return_value=wsgi_utils.FIRST_WORKER_ID).start()
364370
super().setUp()
365371
cfg.CONF.set_override('network_vlan_ranges',
366372
NETWORK_VLAN_RANGES,
@@ -400,7 +406,7 @@ def test__populate_new_default_network_segment_ranges(self):
400406

401407
def test__delete_expired_default_network_segment_ranges(self):
402408
self.driver._delete_expired_default_network_segment_ranges(
403-
self.start_time)
409+
self.context, self.start_time)
404410
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
405411
self.context, network_type=self.driver.get_type())
406412
self.assertEqual(0, len(ret))

0 commit comments

Comments
 (0)