230 lines
9.8 KiB
Python
230 lines
9.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 random
|
|
|
|
from neutron_lib import constants as p_const
|
|
from neutron_lib import context as neutron_ctx
|
|
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 sqlalchemy import and_
|
|
from sqlalchemy import sql
|
|
|
|
from neutron.db.models import network_segment_range as range_model
|
|
from neutron.objects import base as base_obj
|
|
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
IDPOOL_SELECT_SIZE = 100
|
|
|
|
|
|
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__()
|
|
if issubclass(model, base_obj.NeutronDbObject):
|
|
self.model = model.db_model
|
|
else:
|
|
self.model = model
|
|
self.primary_keys = set(dict(self.model.__table__.columns))
|
|
self.primary_keys.remove("allocated")
|
|
|
|
# TODO(ataraday): get rid of this method when old TypeDriver won't be used
|
|
def _get_session(self, arg):
|
|
if isinstance(arg, neutron_ctx.Context):
|
|
return arg.session, db_api.CONTEXT_WRITER.using(arg)
|
|
return arg, arg.session.begin(subtransactions=True)
|
|
|
|
def build_segment_query(self, session, **filters):
|
|
# Only uses filters that correspond to columns defined by this model.
|
|
# Subclasses may use/support additional filters
|
|
columns = set(dict(self.model.__table__.columns))
|
|
model_filters = dict((k, filters[k])
|
|
for k in columns & set(filters.keys()))
|
|
return [session.query(self.model).filter_by(allocated=False,
|
|
**model_filters)]
|
|
|
|
def build_segment_queries_for_tenant_and_shared_ranges(self, session,
|
|
**filters):
|
|
"""Enforces that segments are allocated from network segment ranges
|
|
that are owned by the tenant, and then from shared ranges, but never
|
|
from ranges owned by other tenants.
|
|
This method also enforces that other network segment range attributes
|
|
are used when constraining the set of possible segments to be used.
|
|
"""
|
|
network_type = self.get_type()
|
|
project_id = filters.pop('project_id', None)
|
|
columns = set(dict(self.model.__table__.columns))
|
|
model_filters = dict((k, filters[k])
|
|
for k in columns & set(filters.keys()))
|
|
query = (session.query(self.model)
|
|
.filter_by(allocated=False, **model_filters))
|
|
query = query.join(
|
|
range_model.NetworkSegmentRange,
|
|
and_(range_model.NetworkSegmentRange.network_type == network_type,
|
|
self.model.physical_network ==
|
|
range_model.NetworkSegmentRange.physical_network if
|
|
network_type == p_const.TYPE_VLAN else
|
|
sql.expression.true()))
|
|
query = query.filter(and_(self.model_segmentation_id >=
|
|
range_model.NetworkSegmentRange.minimum,
|
|
self.model_segmentation_id <=
|
|
range_model.NetworkSegmentRange.maximum))
|
|
query_project_id = (query.filter(
|
|
range_model.NetworkSegmentRange.project_id == project_id) if
|
|
project_id is not None else [])
|
|
query_shared = query.filter(
|
|
range_model.NetworkSegmentRange.shared == sql.expression.true())
|
|
|
|
return [query_project_id] + [query_shared]
|
|
|
|
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()
|
|
session, ctx_manager = self._get_session(context)
|
|
|
|
try:
|
|
with ctx_manager:
|
|
alloc = (
|
|
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 = (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(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()
|
|
session, ctx_manager = self._get_session(context)
|
|
with ctx_manager:
|
|
queries = (self.build_segment_queries_for_tenant_and_shared_ranges(
|
|
session, **filters)
|
|
if directory.get_plugin(
|
|
plugin_constants.NETWORK_SEGMENT_RANGE) else
|
|
self.build_segment_query(session, **filters))
|
|
|
|
for select in queries:
|
|
# Selected segment can be allocated before update by someone
|
|
# else
|
|
allocs = select.limit(IDPOOL_SELECT_SIZE).all()
|
|
|
|
if not allocs:
|
|
# No resource available
|
|
continue
|
|
|
|
alloc = random.choice(allocs)
|
|
raw_segment = dict((k, alloc[k]) for k in self.primary_keys)
|
|
LOG.debug("%(type)s segment allocate from pool "
|
|
"started with %(segment)s ",
|
|
{"type": network_type,
|
|
"segment": raw_segment})
|
|
count = (session.query(self.model).
|
|
filter_by(allocated=False, **raw_segment).
|
|
update({"allocated": True}))
|
|
if count:
|
|
LOG.debug("%(type)s segment allocate from pool "
|
|
"success with %(segment)s ",
|
|
{"type": network_type,
|
|
"segment": raw_segment})
|
|
return alloc
|
|
|
|
# Segment allocated since select
|
|
LOG.debug("Allocate %(type)s segment from pool "
|
|
"failed with segment %(segment)s",
|
|
{"type": network_type,
|
|
"segment": raw_segment})
|
|
# saving real exception in case we exceeded amount of attempts
|
|
raise db_exc.RetryRequest(
|
|
exceptions.NoNetworkFoundInMaximumAllowedAttempts())
|