neutron/neutron/plugins/ml2/drivers/helpers.py
Rodolfo Alonso Hernandez 6eaa6d83d7 Randomize segmentation ID assignation
If plugin "network_segment_range" is not enabled and a new segment
is required, if no segmentation ID is provided in the request, the
segmentation ID assigned is randomly retrieved from the non
allocated segmentation IDs.

The goal is to improve the concurrent network (and segment) creation.
If several segments are created in parallel, this random query
will return a different segmentation ID to each one, avoiding the
database retry request.

Closes-Bug: #1920923

Change-Id: Id3f71611a00e69c4f22340ca4d05d95e4373cf69
2021-03-24 13:56:09 +00:00

168 lines
6.8 KiB
Python

# Copyright (c) 2014 Thales Services SAS
# 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.
import functools
from neutron_lib import context
from neutron_lib.db import api as db_api
from neutron_lib import exceptions
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from neutron_lib.plugins.ml2 import api
from neutron_lib.plugins import utils as p_utils
from neutron_lib.utils import helpers
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log
from neutron.objects import network_segment_range as ns_range
LOG = log.getLogger(__name__)
class BaseTypeDriver(api.ML2TypeDriver):
"""BaseTypeDriver for functions common to Segment and flat."""
def __init__(self):
try:
self.physnet_mtus = helpers.parse_mappings(
cfg.CONF.ml2.physical_network_mtus, unique_values=False
)
except Exception as e:
LOG.error("Failed to parse physical_network_mtus: %s", e)
self.physnet_mtus = []
def get_mtu(self, physical_network=None):
return p_utils.get_deployment_physnet_mtu()
class SegmentTypeDriver(BaseTypeDriver):
"""SegmentTypeDriver for segment allocation.
Provide methods helping to perform segment allocation fully or partially
specified.
"""
def __init__(self, model):
super(SegmentTypeDriver, self).__init__()
self.model = model.db_model
self.segmentation_obj = model
primary_keys_columns = self.model.__table__.primary_key.columns
self.primary_keys = {col.name for col in primary_keys_columns}
def allocate_fully_specified_segment(self, context, **raw_segment):
"""Allocate segment fully specified by raw_segment.
If segment exists, then try to allocate it and return db object
If segment does not exists, then try to create it and return db object
If allocation/creation failed, then return None
"""
network_type = self.get_type()
try:
with db_api.CONTEXT_WRITER.using(context):
alloc = (
context.session.query(self.model).filter_by(**raw_segment).
first())
if alloc:
if alloc.allocated:
# Segment already allocated
return
else:
# Segment not allocated
LOG.debug("%(type)s segment %(segment)s allocate "
"started ",
{"type": network_type,
"segment": raw_segment})
count = (context.session.query(self.model).
filter_by(allocated=False, **raw_segment).
update({"allocated": True}))
if count:
LOG.debug("%(type)s segment %(segment)s allocate "
"done ",
{"type": network_type,
"segment": raw_segment})
return alloc
# Segment allocated or deleted since select
LOG.debug("%(type)s segment %(segment)s allocate "
"failed: segment has been allocated or "
"deleted",
{"type": network_type,
"segment": raw_segment})
# Segment to create or already allocated
LOG.debug("%(type)s segment %(segment)s create started",
{"type": network_type, "segment": raw_segment})
alloc = self.model(allocated=True, **raw_segment)
alloc.save(context.session)
LOG.debug("%(type)s segment %(segment)s create done",
{"type": network_type, "segment": raw_segment})
except db_exc.DBDuplicateEntry:
# Segment already allocated (insert failure)
alloc = None
LOG.debug("%(type)s segment %(segment)s create failed",
{"type": network_type, "segment": raw_segment})
return alloc
def allocate_partially_specified_segment(self, context, **filters):
"""Allocate model segment from pool partially specified by filters.
Return allocated db object or None.
"""
network_type = self.get_type()
if directory.get_plugin(plugin_constants.NETWORK_SEGMENT_RANGE):
calls = [
functools.partial(
ns_range.NetworkSegmentRange.get_segments_for_project,
context, self.model, network_type,
self.model_segmentation_id, **filters),
functools.partial(
ns_range.NetworkSegmentRange.get_segments_shared,
context, self.model, network_type,
self.model_segmentation_id, **filters)]
else:
calls = [functools.partial(
self.segmentation_obj.get_random_unallocated_segment,
context, **filters)]
try_to_allocate = False
for call in calls:
allocations = call()
if not isinstance(allocations, list):
allocations = [allocations] if allocations else []
for alloc in allocations:
segment = dict((k, alloc[k]) for k in self.primary_keys)
try_to_allocate = True
if self.segmentation_obj.allocate(context, **segment):
LOG.debug('%(type)s segment allocate from pool success '
'with %(segment)s ', {'type': network_type,
'segment': segment})
return alloc
if try_to_allocate:
raise db_exc.RetryRequest(
exceptions.NoNetworkFoundInMaximumAllowedAttempts())
@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)