
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
168 lines
6.8 KiB
Python
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)
|