Synchronize the network segment range initialization
The VLAN and tunnelled network segment range initialization is performed in each Neutron API worker during the initialization transient period. This patch adds the following optimizations: * The ``network_segment_ranges`` expired register deletion is based on the Neutron API start time, that is unique for all workers. The new registers created by other API workers won't be deleted. * A new method ``NetworkSegmentRange.new_default`` is used to create the new default registers that is process-safe, using the database transactional engine. Closes-Bug: #2086602 Change-Id: Id1551dcd786202384393556e15e91de72aa7272e
This commit is contained in:
@@ -1125,3 +1125,8 @@ def read_file(path: str) -> str:
|
||||
def ts_to_datetime(timestamp):
|
||||
"""Converts timestamp (in seconds) to datetime"""
|
||||
return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
|
||||
|
||||
|
||||
def datetime_to_ts(_datetime):
|
||||
"""Converts datetime to timestamp in seconds"""
|
||||
return int(datetime.datetime.timestamp(_datetime))
|
||||
|
||||
@@ -13,20 +13,34 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_utils import timeutils
|
||||
|
||||
def get_start_time():
|
||||
from neutron.common import utils
|
||||
|
||||
|
||||
def get_start_time(default=None, current_time=False):
|
||||
"""Return the 'start-time=%t' config varible in the WSGI config
|
||||
|
||||
This variable contains the start time of the WSGI server. Check
|
||||
https://uwsgi-docs.readthedocs.io/en/latest/Configuration.html
|
||||
#magic-variables
|
||||
|
||||
:param default: (int or float) in case the uwsgi option 'start-time' is not
|
||||
available or the uwsgi module cannot be loaded, the method
|
||||
will return this value.
|
||||
:param current_time: (bool) if ``default`` is None and this flag is set,
|
||||
the method will return the current time.
|
||||
:return: (int) start time in seconds.
|
||||
"""
|
||||
if not default and current_time:
|
||||
default = utils.datetime_to_ts(timeutils.utcnow())
|
||||
default = int(default) if default else None
|
||||
try:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import uwsgi
|
||||
start_time = uwsgi.opt.get('start-time')
|
||||
if not start_time:
|
||||
return
|
||||
return default
|
||||
return int(start_time.decode(encoding='utf-8'))
|
||||
except ImportError:
|
||||
return
|
||||
return default
|
||||
|
||||
@@ -21,6 +21,7 @@ from neutron_lib.db import resource_extend
|
||||
from neutron_lib.db import utils as db_utils
|
||||
from neutron_lib import exceptions as n_exc
|
||||
from neutron_lib.objects import common_types
|
||||
from oslo_utils import uuidutils
|
||||
from oslo_versionedobjects import fields as obj_fields
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy import not_
|
||||
@@ -29,6 +30,7 @@ from sqlalchemy import sql
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.common import _constants as common_constants
|
||||
from neutron.common import utils as n_utils
|
||||
from neutron.db.models import network_segment_range as range_model
|
||||
from neutron.db.models.plugins.ml2 import geneveallocation as \
|
||||
geneve_alloc_model
|
||||
@@ -229,3 +231,46 @@ class NetworkSegmentRange(base.NeutronDbObject):
|
||||
for _range in segment_ranges]
|
||||
query = query.filter(or_(*clauses))
|
||||
return query.limit(common_constants.IDPOOL_SELECT_SIZE).all()
|
||||
|
||||
@classmethod
|
||||
def delete_expired_default_network_segment_ranges(
|
||||
cls, context, network_type, start_time):
|
||||
model = models_map.get(network_type)
|
||||
if not model:
|
||||
msg = (_("network_type '%s' unknown for getting allocation "
|
||||
"information") % network_type)
|
||||
raise n_exc.InvalidInput(error_message=msg)
|
||||
created_at = n_utils.ts_to_datetime(start_time)
|
||||
with cls.db_context_writer(context):
|
||||
nsr_ids = context.session.query(cls.db_model.id).filter(
|
||||
cls.db_model.default == sql.expression.true(),
|
||||
cls.db_model.network_type == network_type,
|
||||
cls.db_model.created_at != created_at).all()
|
||||
nsr_ids = [nsr_id[0] for nsr_id in nsr_ids]
|
||||
if nsr_ids:
|
||||
NetworkSegmentRange.delete_objects(context, id=nsr_ids)
|
||||
|
||||
@classmethod
|
||||
def new_default(cls, context, network_type, physical_network,
|
||||
minimum, maximum, start_time):
|
||||
model = models_map.get(network_type)
|
||||
if not model:
|
||||
msg = (_("network_type '%s' unknown for getting allocation "
|
||||
"information") % network_type)
|
||||
raise n_exc.InvalidInput(error_message=msg)
|
||||
created_at = n_utils.ts_to_datetime(start_time)
|
||||
with cls.db_context_writer(context):
|
||||
if context.session.query(cls.db_model).filter(
|
||||
cls.db_model.default == sql.expression.true(),
|
||||
cls.db_model.shared == sql.expression.true(),
|
||||
cls.db_model.network_type == network_type,
|
||||
cls.db_model.physical_network == physical_network,
|
||||
cls.db_model.minimum == minimum,
|
||||
cls.db_model.maximum == maximum,
|
||||
cls.db_model.created_at == created_at,
|
||||
).count():
|
||||
return
|
||||
|
||||
cls(context, id=uuidutils.generate_uuid(), default=True, shared=True,
|
||||
network_type=network_type, physical_network=physical_network,
|
||||
minimum=minimum, maximum=maximum, created_at=created_at).create()
|
||||
|
||||
@@ -166,8 +166,7 @@ class SegmentTypeDriver(BaseTypeDriver):
|
||||
LOG.debug(' - %s', srange)
|
||||
|
||||
@db_api.retry_db_errors
|
||||
def _delete_expired_default_network_segment_ranges(self):
|
||||
ctx = context.get_admin_context()
|
||||
with db_api.CONTEXT_WRITER.using(ctx):
|
||||
filters = {'default': True, 'network_type': self.get_type()}
|
||||
ns_range.NetworkSegmentRange.delete_objects(ctx, **filters)
|
||||
def _delete_expired_default_network_segment_ranges(self, start_time):
|
||||
ns_range.NetworkSegmentRange.\
|
||||
delete_expired_default_network_segment_ranges(
|
||||
context.get_admin_context(), self.get_type(), start_time)
|
||||
|
||||
@@ -29,7 +29,6 @@ from neutron_lib.plugins import utils as plugin_utils
|
||||
from oslo_config import cfg
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_log import log
|
||||
from oslo_utils import uuidutils
|
||||
from sqlalchemy import or_
|
||||
|
||||
from neutron._i18n import _
|
||||
@@ -146,21 +145,12 @@ class _TunnelTypeDriverBase(helpers.SegmentTypeDriver, metaclass=abc.ABCMeta):
|
||||
{'type': self.get_type(), 'range': current_range})
|
||||
|
||||
@db_api.retry_db_errors
|
||||
def _populate_new_default_network_segment_ranges(self):
|
||||
def _populate_new_default_network_segment_ranges(self, start_time):
|
||||
ctx = context.get_admin_context()
|
||||
for tun_min, tun_max in self.tunnel_ranges:
|
||||
res = {
|
||||
'id': uuidutils.generate_uuid(),
|
||||
'name': '',
|
||||
'default': True,
|
||||
'shared': True,
|
||||
'network_type': self.get_type(),
|
||||
'minimum': tun_min,
|
||||
'maximum': tun_max}
|
||||
with db_api.CONTEXT_WRITER.using(ctx):
|
||||
new_default_range_obj = (
|
||||
range_obj.NetworkSegmentRange(ctx, **res))
|
||||
new_default_range_obj.create()
|
||||
with db_api.CONTEXT_WRITER.using(ctx):
|
||||
for tun_min, tun_max in self.tunnel_ranges:
|
||||
range_obj.NetworkSegmentRange.new_default(
|
||||
ctx, self.get_type(), None, tun_min, tun_max, start_time)
|
||||
|
||||
@db_api.retry_db_errors
|
||||
def _get_network_segment_ranges_from_db(self):
|
||||
@@ -174,9 +164,9 @@ class _TunnelTypeDriverBase(helpers.SegmentTypeDriver, metaclass=abc.ABCMeta):
|
||||
|
||||
return ranges
|
||||
|
||||
def initialize_network_segment_range_support(self):
|
||||
self._delete_expired_default_network_segment_ranges()
|
||||
self._populate_new_default_network_segment_ranges()
|
||||
def initialize_network_segment_range_support(self, start_time):
|
||||
self._delete_expired_default_network_segment_ranges(start_time)
|
||||
self._populate_new_default_network_segment_ranges(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
|
||||
|
||||
@@ -26,7 +26,6 @@ from neutron_lib.plugins.ml2 import api
|
||||
from neutron_lib.plugins import utils as plugin_utils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.conf.plugins.ml2.drivers import driver_type
|
||||
@@ -58,24 +57,15 @@ class VlanTypeDriver(helpers.SegmentTypeDriver):
|
||||
self._parse_network_vlan_ranges()
|
||||
|
||||
@db_api.retry_db_errors
|
||||
def _populate_new_default_network_segment_ranges(self):
|
||||
def _populate_new_default_network_segment_ranges(self, start_time):
|
||||
ctx = context.get_admin_context()
|
||||
for (physical_network, vlan_ranges) in (
|
||||
self.network_vlan_ranges.items()):
|
||||
for vlan_min, vlan_max in vlan_ranges:
|
||||
res = {
|
||||
'id': uuidutils.generate_uuid(),
|
||||
'name': '',
|
||||
'default': True,
|
||||
'shared': True,
|
||||
'network_type': p_const.TYPE_VLAN,
|
||||
'physical_network': physical_network,
|
||||
'minimum': vlan_min,
|
||||
'maximum': vlan_max}
|
||||
with db_api.CONTEXT_WRITER.using(ctx):
|
||||
new_default_range_obj = (
|
||||
range_obj.NetworkSegmentRange(ctx, **res))
|
||||
new_default_range_obj.create()
|
||||
with db_api.CONTEXT_WRITER.using(ctx):
|
||||
for (physical_network, vlan_ranges) in (
|
||||
self.network_vlan_ranges.items()):
|
||||
for vlan_min, vlan_max in vlan_ranges:
|
||||
range_obj.NetworkSegmentRange.new_default(
|
||||
ctx, self.get_type(), physical_network, vlan_min,
|
||||
vlan_max, start_time)
|
||||
|
||||
def _parse_network_vlan_ranges(self):
|
||||
try:
|
||||
@@ -181,9 +171,9 @@ class VlanTypeDriver(helpers.SegmentTypeDriver):
|
||||
self._sync_vlan_allocations()
|
||||
LOG.info("VlanTypeDriver initialization complete")
|
||||
|
||||
def initialize_network_segment_range_support(self):
|
||||
self._delete_expired_default_network_segment_ranges()
|
||||
self._populate_new_default_network_segment_ranges()
|
||||
def initialize_network_segment_range_support(self, start_time):
|
||||
self._delete_expired_default_network_segment_ranges(start_time)
|
||||
self._populate_new_default_network_segment_ranges(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
|
||||
|
||||
@@ -203,12 +203,12 @@ class TypeManager(stevedore.named.NamedExtensionManager):
|
||||
LOG.info("Initializing driver for type '%s'", network_type)
|
||||
driver.obj.initialize()
|
||||
|
||||
def initialize_network_segment_range_support(self):
|
||||
def initialize_network_segment_range_support(self, start_time):
|
||||
for network_type, driver in self.drivers.items():
|
||||
if network_type in constants.NETWORK_SEGMENT_RANGE_TYPES:
|
||||
LOG.info("Initializing driver network segment range support "
|
||||
"for type '%s'", network_type)
|
||||
driver.obj.initialize_network_segment_range_support()
|
||||
driver.obj.initialize_network_segment_range_support(start_time)
|
||||
|
||||
def _add_network_segment(self, context, network_id, segment,
|
||||
segment_index=0):
|
||||
|
||||
@@ -25,6 +25,7 @@ from oslo_log import helpers as log_helpers
|
||||
from oslo_log import log
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.common import wsgi_utils
|
||||
from neutron.db import segments_db
|
||||
from neutron.extensions import network_segment_range as ext_range
|
||||
from neutron.objects import base as base_obj
|
||||
@@ -65,8 +66,10 @@ class NetworkSegmentRangePlugin(ext_range.NetworkSegmentRangePluginBase):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._start_time = wsgi_utils.get_start_time(current_time=True)
|
||||
self.type_manager = directory.get_plugin().type_manager
|
||||
self.type_manager.initialize_network_segment_range_support()
|
||||
self.type_manager.initialize_network_segment_range_support(
|
||||
self._start_time)
|
||||
|
||||
def _get_network_segment_range(self, context, id):
|
||||
obj = obj_network_segment_range.NetworkSegmentRange.get_object(
|
||||
|
||||
@@ -19,8 +19,10 @@ from unittest import mock
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import exceptions as n_exc
|
||||
from neutron_lib.utils import helpers
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.common import utils as n_utils
|
||||
from neutron.objects import network as net_obj
|
||||
from neutron.objects import network_segment_range
|
||||
from neutron.objects.plugins.ml2 import base as ml2_base
|
||||
@@ -107,15 +109,19 @@ class NetworkSegmentRangeDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
||||
|
||||
def _create_network_segment_range(
|
||||
self, minimum, maximum, network_type=None, physical_network=None,
|
||||
project_id=None, default=False, shared=False):
|
||||
project_id=None, default=False, shared=False, start_time=None):
|
||||
kwargs = self.get_random_db_fields()
|
||||
created_at = (n_utils.ts_to_datetime(start_time) if start_time else
|
||||
timeutils.utcnow())
|
||||
kwargs.update({'network_type': network_type or constants.TYPE_VLAN,
|
||||
'physical_network': physical_network or 'foo',
|
||||
'minimum': minimum,
|
||||
'maximum': maximum,
|
||||
'default': default,
|
||||
'shared': shared,
|
||||
'project_id': project_id})
|
||||
'project_id': project_id,
|
||||
'created_at': created_at,
|
||||
})
|
||||
db_obj = self._test_class.db_model(**kwargs)
|
||||
obj_fields = self._test_class.modify_fields_from_db(db_obj)
|
||||
obj = self._test_class(self.context, **obj_fields)
|
||||
@@ -398,3 +404,45 @@ class NetworkSegmentRangeDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
||||
self.assertEqual(len(available_ids), len(allocations))
|
||||
for alloc in allocations:
|
||||
self.assertIn(alloc.segmentation_id, available_ids)
|
||||
|
||||
def test_delete_expired_default_network_segment_ranges(self):
|
||||
start_time = n_utils.datetime_to_ts(timeutils.utcnow())
|
||||
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".
|
||||
ranges = network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context, default=True, shared=True,
|
||||
network_type=network_type)
|
||||
self.assertEqual(1, len(ranges))
|
||||
|
||||
def test_new_default(self):
|
||||
start_time = n_utils.datetime_to_ts(timeutils.utcnow())
|
||||
for network_type in network_segment_range.models_map.keys():
|
||||
physical_network = ('foo' if network_type == constants.TYPE_VLAN
|
||||
else None)
|
||||
ranges = network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context, network_type=network_type)
|
||||
self.assertEqual(0, len(ranges))
|
||||
|
||||
# The method "new_default" is idempotent, call it twice.
|
||||
for _ in range(2):
|
||||
network_segment_range.NetworkSegmentRange.new_default(
|
||||
self.context, network_type, physical_network,
|
||||
1, 10, start_time)
|
||||
ranges = network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context, network_type=network_type)
|
||||
self.assertEqual(1, len(ranges))
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import random
|
||||
from unittest import mock
|
||||
|
||||
from neutron_lib import constants as p_const
|
||||
@@ -477,13 +478,14 @@ class TunnelTypeNetworkSegmentRangeTestMixin:
|
||||
cfg.CONF.set_override('service_plugins', [SERVICE_PLUGIN_KLASS])
|
||||
self.context = context.Context()
|
||||
self.driver = self.DRIVER_CLASS()
|
||||
self.start_time = random.randint(10**5, 10**6)
|
||||
|
||||
def test__populate_new_default_network_segment_ranges(self):
|
||||
# _populate_new_default_network_segment_ranges will be called when
|
||||
# the type driver initializes with `network_segment_range` loaded as
|
||||
# one of the `service_plugins`
|
||||
self.driver._initialize(RAW_TUNNEL_RANGES)
|
||||
self.driver.initialize_network_segment_range_support()
|
||||
self.driver.initialize_network_segment_range_support(self.start_time)
|
||||
self.driver.sync_allocations()
|
||||
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context)
|
||||
@@ -501,7 +503,8 @@ class TunnelTypeNetworkSegmentRangeTestMixin:
|
||||
def test__delete_expired_default_network_segment_ranges(self):
|
||||
self.driver.tunnel_ranges = TUNNEL_RANGES
|
||||
self.driver.sync_allocations()
|
||||
self.driver._delete_expired_default_network_segment_ranges()
|
||||
self.driver._delete_expired_default_network_segment_ranges(
|
||||
self.start_time)
|
||||
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context)
|
||||
self.context, network_type=self.driver.get_type())
|
||||
self.assertEqual(0, len(ret))
|
||||
|
||||
@@ -19,8 +19,10 @@ from neutron_lib import context
|
||||
from neutron_lib.plugins import utils as plugin_utils
|
||||
from oslo_config import cfg
|
||||
from oslo_db import exception as exc
|
||||
from oslo_utils import timeutils
|
||||
from sqlalchemy.orm import query
|
||||
|
||||
from neutron.common import utils as n_utils
|
||||
from neutron.plugins.ml2.drivers import type_vlan
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
@@ -152,5 +154,6 @@ class HelpersTestWithNetworkSegmentRange(HelpersTest):
|
||||
NETWORK_VLAN_RANGES_CFG_ENTRIES)
|
||||
self.context = context.get_admin_context()
|
||||
self.driver = type_vlan.VlanTypeDriver()
|
||||
self.driver.initialize_network_segment_range_support()
|
||||
start_time = n_utils.datetime_to_ts(timeutils.utcnow())
|
||||
self.driver.initialize_network_segment_range_support(start_time)
|
||||
self.driver._sync_vlan_allocations()
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import random
|
||||
from unittest import mock
|
||||
|
||||
from neutron_lib import constants as p_const
|
||||
@@ -371,6 +372,7 @@ class VlanTypeTestWithNetworkSegmentRange(testlib_api.SqlTestCase):
|
||||
self.driver._sync_vlan_allocations()
|
||||
self.context = context.Context()
|
||||
self.setup_coreplugin(CORE_PLUGIN)
|
||||
self.start_time = random.randint(10**5, 10**6)
|
||||
|
||||
def test__populate_new_default_network_segment_ranges(self):
|
||||
# _populate_new_default_network_segment_ranges will be called when
|
||||
@@ -397,7 +399,8 @@ class VlanTypeTestWithNetworkSegmentRange(testlib_api.SqlTestCase):
|
||||
self.assertEqual(VLAN_MAX, network_segment_range.maximum)
|
||||
|
||||
def test__delete_expired_default_network_segment_ranges(self):
|
||||
self.driver._delete_expired_default_network_segment_ranges()
|
||||
self.driver._delete_expired_default_network_segment_ranges(
|
||||
self.start_time)
|
||||
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
||||
self.context)
|
||||
self.context, network_type=self.driver.get_type())
|
||||
self.assertEqual(0, len(ret))
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
other:
|
||||
- |
|
||||
The ``network_segment_ranges`` registers are now initialized based on the
|
||||
Neutron API start time. The type driver class cleans up the database
|
||||
for those registers not matching the network type and the "created_at"
|
||||
timestamp and uses the process-safe method
|
||||
``NetworkSegmentRange.new_default`` to create the new registers.
|
||||
Reference in New Issue
Block a user