Use network segment ranges for segment allocation

This patch makes necessary changes to ML2 type drivers and plugin
manager for network segment range extension support when it is loaded.

When the network segment range extension is not loaded, no impact to the
current flow.

When the extension is loaded,
- populating a range that is managed from the configuration file [1]_,
  such as "VLAN IDs", "VXLAN VNI IDs", "GRE tunnel IDs",
  "Geneve VNI IDs" to the network segment range DB table as a "default"
  and "shared" entry to maintain backward compatibility;
- reloading the "default" segment ranges when Neutron server
  starts/restarts;
- creating a set of "default" network segment ranges out of the
  ML2-config-file-defined ranges [1]_ and the segment allocation
  operations are always retrieving the information from the DB to have
  the network segment ranges fully administered via API;
- when a tenant allocates a segment, it will first allocate from an
  available segment range assigned to the tenant, and then a shared
  range if no tenant specific allocation is possible.

[1] /etc/neutron/plugins/ml2/ml2_conf.ini

Co-authored-by: Allain Legacy <Allain.legacy@windriver.com>

Partially-implements: blueprint network-segment-range-management
Change-Id: I522940fc4d054f5eec1110eb2c424e32e8ae6bad
This commit is contained in:
Kailun Qin 2019-03-05 18:47:08 +08:00 committed by Slawek Kaplonski
parent fe73e8c9b3
commit a01b7125cd
16 changed files with 472 additions and 65 deletions

View File

@ -15,16 +15,22 @@
import random import random
from neutron_lib import constants as p_const
from neutron_lib import context as neutron_ctx from neutron_lib import context as neutron_ctx
from neutron_lib.db import api as db_api from neutron_lib.db import api as db_api
from neutron_lib import exceptions 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.ml2 import api
from neutron_lib.plugins import utils as p_utils from neutron_lib.plugins import utils as p_utils
from neutron_lib.utils import helpers from neutron_lib.utils import helpers
from oslo_config import cfg from oslo_config import cfg
from oslo_db import exception as db_exc from oslo_db import exception as db_exc
from oslo_log import log 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 from neutron.objects import base as base_obj
@ -71,6 +77,49 @@ class SegmentTypeDriver(BaseTypeDriver):
return arg.session, db_api.CONTEXT_WRITER.using(arg) return arg.session, db_api.CONTEXT_WRITER.using(arg)
return arg, arg.session.begin(subtransactions=True) 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): def allocate_fully_specified_segment(self, context, **raw_segment):
"""Allocate segment fully specified by raw_segment. """Allocate segment fully specified by raw_segment.
@ -139,15 +188,20 @@ class SegmentTypeDriver(BaseTypeDriver):
network_type = self.get_type() network_type = self.get_type()
session, ctx_manager = self._get_session(context) session, ctx_manager = self._get_session(context)
with ctx_manager: with ctx_manager:
select = (session.query(self.model). queries = (self.build_segment_queries_for_tenant_and_shared_ranges(
filter_by(allocated=False, **filters)) session, **filters)
if directory.get_plugin(
plugin_constants.NETWORK_SEGMENT_RANGE) else
self.build_segment_query(session, **filters))
# Selected segment can be allocated before update by someone else, for select in queries:
# Selected segment can be allocated before update by someone
# else
allocs = select.limit(IDPOOL_SELECT_SIZE).all() allocs = select.limit(IDPOOL_SELECT_SIZE).all()
if not allocs: if not allocs:
# No resource available # No resource available
return continue
alloc = random.choice(allocs) alloc = random.choice(allocs)
raw_segment = dict((k, alloc[k]) for k in self.primary_keys) raw_segment = dict((k, alloc[k]) for k in self.primary_keys)

View File

@ -63,6 +63,15 @@ class FlatTypeDriver(helpers.BaseTypeDriver):
def initialize(self): def initialize(self):
LOG.info("ML2 FlatTypeDriver initialization complete") LOG.info("ML2 FlatTypeDriver initialization complete")
def initialize_network_segment_range_support(self):
pass
def update_network_segment_range_allocations(self):
pass
def get_network_segment_ranges(self):
pass
def is_partial_segment(self, segment): def is_partial_segment(self, segment):
return False return False
@ -85,7 +94,7 @@ class FlatTypeDriver(helpers.BaseTypeDriver):
msg = _("%s prohibited for flat provider network") % key msg = _("%s prohibited for flat provider network") % key
raise exc.InvalidInput(error_message=msg) raise exc.InvalidInput(error_message=msg)
def reserve_provider_segment(self, context, segment): def reserve_provider_segment(self, context, segment, filters=None):
physical_network = segment[api.PHYSICAL_NETWORK] physical_network = segment[api.PHYSICAL_NETWORK]
try: try:
LOG.debug("Reserving flat network on physical " LOG.debug("Reserving flat network on physical "
@ -100,7 +109,7 @@ class FlatTypeDriver(helpers.BaseTypeDriver):
segment[api.MTU] = self.get_mtu(alloc.physical_network) segment[api.MTU] = self.get_mtu(alloc.physical_network)
return segment return segment
def allocate_tenant_segment(self, context): def allocate_tenant_segment(self, context, filters=None):
# Tenant flat networks are not supported. # Tenant flat networks are not supported.
return return

View File

@ -20,6 +20,8 @@ from oslo_config import cfg
from oslo_log import log from oslo_log import log
from neutron.conf.plugins.ml2.drivers import driver_type from neutron.conf.plugins.ml2.drivers import driver_type
from neutron.db.models.plugins.ml2 import geneveallocation as \
geneve_alloc_model
from neutron.objects.plugins.ml2 import geneveallocation as geneve_obj from neutron.objects.plugins.ml2 import geneveallocation as geneve_obj
from neutron.plugins.ml2.drivers import type_tunnel from neutron.plugins.ml2.drivers import type_tunnel
@ -34,6 +36,8 @@ class GeneveTypeDriver(type_tunnel.EndpointTunnelTypeDriver):
super(GeneveTypeDriver, self).__init__(geneve_obj.GeneveAllocation, super(GeneveTypeDriver, self).__init__(geneve_obj.GeneveAllocation,
geneve_obj.GeneveEndpoint) geneve_obj.GeneveEndpoint)
self.max_encap_size = cfg.CONF.ml2_type_geneve.max_header_size self.max_encap_size = cfg.CONF.ml2_type_geneve.max_header_size
self.model_segmentation_id = (
geneve_alloc_model.GeneveAllocation.geneve_vni)
def get_type(self): def get_type(self):
return p_const.TYPE_GENEVE return p_const.TYPE_GENEVE

View File

@ -19,6 +19,8 @@ from oslo_config import cfg
from oslo_log import log from oslo_log import log
from neutron.conf.plugins.ml2.drivers import driver_type from neutron.conf.plugins.ml2.drivers import driver_type
from neutron.db.models.plugins.ml2 import gre_allocation_endpoints as \
gre_alloc_model
from neutron.objects.plugins.ml2 import greallocation as gre_obj from neutron.objects.plugins.ml2 import greallocation as gre_obj
from neutron.plugins.ml2.drivers import type_tunnel from neutron.plugins.ml2.drivers import type_tunnel
@ -32,6 +34,7 @@ class GreTypeDriver(type_tunnel.EndpointTunnelTypeDriver):
def __init__(self): def __init__(self):
super(GreTypeDriver, self).__init__( super(GreTypeDriver, self).__init__(
gre_obj.GreAllocation, gre_obj.GreEndpoint) gre_obj.GreAllocation, gre_obj.GreEndpoint)
self.model_segmentation_id = gre_alloc_model.GreAllocation.gre_id
def get_type(self): def get_type(self):
return p_const.TYPE_GRE return p_const.TYPE_GRE

View File

@ -42,6 +42,15 @@ class LocalTypeDriver(api.ML2TypeDriver):
def initialize(self): def initialize(self):
pass pass
def initialize_network_segment_range_support(self):
pass
def update_network_segment_range_allocations(self):
pass
def get_network_segment_ranges(self):
pass
def is_partial_segment(self, segment): def is_partial_segment(self, segment):
return False return False
@ -51,11 +60,11 @@ class LocalTypeDriver(api.ML2TypeDriver):
msg = _("%s prohibited for local provider network") % key msg = _("%s prohibited for local provider network") % key
raise exc.InvalidInput(error_message=msg) raise exc.InvalidInput(error_message=msg)
def reserve_provider_segment(self, context, segment): def reserve_provider_segment(self, context, segment, filters=None):
# No resources to reserve # No resources to reserve
return segment return segment
def allocate_tenant_segment(self, context): def allocate_tenant_segment(self, context, filters=None):
# No resources to allocate # No resources to allocate
return {api.NETWORK_TYPE: p_const.TYPE_LOCAL} return {api.NETWORK_TYPE: p_const.TYPE_LOCAL}

View File

@ -22,18 +22,23 @@ from neutron_lib import constants as p_const
from neutron_lib import context from neutron_lib import context
from neutron_lib.db import api as db_api from neutron_lib.db import api as db_api
from neutron_lib import exceptions as exc from neutron_lib import exceptions as exc
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.ml2 import api
from neutron_lib.plugins import utils as plugin_utils from neutron_lib.plugins import utils as plugin_utils
from oslo_config import cfg from oslo_config import cfg
from oslo_db import exception as db_exc from oslo_db import exception as db_exc
from oslo_log import log from oslo_log import log
from oslo_utils import uuidutils
import six import six
from six import moves from six import moves
from sqlalchemy import or_ from sqlalchemy import or_
from neutron._i18n import _ from neutron._i18n import _
from neutron.objects import base as base_obj from neutron.objects import base as base_obj
from neutron.objects import network_segment_range as range_obj
from neutron.plugins.ml2.drivers import helpers from neutron.plugins.ml2.drivers import helpers
from neutron.services.network_segment_range import plugin as range_plugin
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -119,6 +124,14 @@ class _TunnelTypeDriverBase(helpers.SegmentTypeDriver):
def _initialize(self, raw_tunnel_ranges): def _initialize(self, raw_tunnel_ranges):
self.tunnel_ranges = [] self.tunnel_ranges = []
self._parse_tunnel_ranges(raw_tunnel_ranges, self.tunnel_ranges) self._parse_tunnel_ranges(raw_tunnel_ranges, self.tunnel_ranges)
if not range_plugin.is_network_segment_range_enabled():
# service plugins are initialized/loaded after the ML2 driver
# initialization. Thus, we base on the information whether
# ``network_segment_range`` service plugin is enabled/defined in
# ``neutron.conf`` to decide whether to skip the first time sync
# allocation during driver initialization, instead of using the
# directory.get_plugin() method - the normal way used elsewhere to
# check if a plugin is loaded.
self.sync_allocations() self.sync_allocations()
def _parse_tunnel_ranges(self, tunnel_ranges, current_range): def _parse_tunnel_ranges(self, tunnel_ranges, current_range):
@ -136,11 +149,67 @@ class _TunnelTypeDriverBase(helpers.SegmentTypeDriver):
LOG.info("%(type)s ID ranges: %(range)s", LOG.info("%(type)s ID ranges: %(range)s",
{'type': self.get_type(), 'range': current_range}) {'type': self.get_type(), 'range': current_range})
@db_api.retry_db_errors
def _populate_new_default_network_segment_ranges(self):
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()
@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(),
}
old_default_range_objs = range_obj.NetworkSegmentRange.get_objects(
ctx, **filters)
for obj in old_default_range_objs:
obj.delete()
@db_api.retry_db_errors
def _get_network_segment_ranges_from_db(self):
ranges = []
ctx = context.get_admin_context()
with db_api.CONTEXT_READER.using(ctx):
range_objs = (range_obj.NetworkSegmentRange.get_objects(
ctx, network_type=self.get_type()))
for obj in range_objs:
ranges.append((obj['minimum'], obj['maximum']))
return ranges
def initialize_network_segment_range_support(self):
self._delete_expired_default_network_segment_ranges()
self._populate_new_default_network_segment_ranges()
# 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
# initialization time.
self.tunnel_ranges = self._get_network_segment_ranges_from_db()
self.sync_allocations()
def update_network_segment_range_allocations(self):
self.sync_allocations()
@db_api.retry_db_errors @db_api.retry_db_errors
def sync_allocations(self): def sync_allocations(self):
# determine current configured allocatable tunnel ids # determine current configured allocatable tunnel ids
tunnel_ids = set() tunnel_ids = set()
for tun_min, tun_max in self.tunnel_ranges: ranges = self.get_network_segment_ranges()
for tun_min, tun_max in ranges:
tunnel_ids |= set(moves.range(tun_min, tun_max + 1)) tunnel_ids |= set(moves.range(tun_min, tun_max + 1))
tunnel_id_getter = operator.attrgetter(self.segmentation_key) tunnel_id_getter = operator.attrgetter(self.segmentation_key)
@ -198,6 +267,19 @@ class _TunnelTypeDriverBase(helpers.SegmentTypeDriver):
ip_header_length = p_const.IP_HEADER_LENGTH[version] ip_header_length = p_const.IP_HEADER_LENGTH[version]
return min(mtu) - ip_header_length if mtu else 0 return min(mtu) - ip_header_length if mtu else 0
def get_network_segment_ranges(self):
"""Get the driver network segment ranges.
Queries all tunnel network segment ranges from DB if the
``NETWORK_SEGMENT_RANGE`` service plugin is enabled. Otherwise,
they will be loaded from the host config file - `ml2_conf.ini`.
"""
ranges = self.tunnel_ranges
if directory.get_plugin(plugin_constants.NETWORK_SEGMENT_RANGE):
ranges = self._get_network_segment_ranges_from_db()
return ranges
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class TunnelTypeDriver(_TunnelTypeDriverBase): class TunnelTypeDriver(_TunnelTypeDriverBase):
@ -213,9 +295,11 @@ class TunnelTypeDriver(_TunnelTypeDriverBase):
- get_allocation - get_allocation
""" """
def reserve_provider_segment(self, session, segment): def reserve_provider_segment(self, session, segment, filters=None):
if self.is_partial_segment(segment): if self.is_partial_segment(segment):
alloc = self.allocate_partially_specified_segment(session) filters = filters or {}
alloc = self.allocate_partially_specified_segment(session,
**filters)
if not alloc: if not alloc:
raise exc.NoNetworkAvailable() raise exc.NoNetworkAvailable()
else: else:
@ -229,8 +313,9 @@ class TunnelTypeDriver(_TunnelTypeDriverBase):
api.SEGMENTATION_ID: getattr(alloc, self.segmentation_key), api.SEGMENTATION_ID: getattr(alloc, self.segmentation_key),
api.MTU: self.get_mtu()} api.MTU: self.get_mtu()}
def allocate_tenant_segment(self, session): def allocate_tenant_segment(self, session, filters=None):
alloc = self.allocate_partially_specified_segment(session) filters = filters or {}
alloc = self.allocate_partially_specified_segment(session, **filters)
if not alloc: if not alloc:
return return
return {api.NETWORK_TYPE: self.get_type(), return {api.NETWORK_TYPE: self.get_type(),
@ -241,7 +326,9 @@ class TunnelTypeDriver(_TunnelTypeDriverBase):
def release_segment(self, session, segment): def release_segment(self, session, segment):
tunnel_id = segment[api.SEGMENTATION_ID] tunnel_id = segment[api.SEGMENTATION_ID]
inside = any(lo <= tunnel_id <= hi for lo, hi in self.tunnel_ranges) ranges = self.get_network_segment_ranges()
inside = any(lo <= tunnel_id <= hi for lo, hi in ranges)
info = {'type': self.get_type(), 'id': tunnel_id} info = {'type': self.get_type(), 'id': tunnel_id}
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
@ -281,9 +368,11 @@ class ML2TunnelTypeDriver(_TunnelTypeDriverBase):
- get_allocation - get_allocation
""" """
def reserve_provider_segment(self, context, segment): def reserve_provider_segment(self, context, segment, filters=None):
if self.is_partial_segment(segment): if self.is_partial_segment(segment):
alloc = self.allocate_partially_specified_segment(context) filters = filters or {}
alloc = self.allocate_partially_specified_segment(context,
**filters)
if not alloc: if not alloc:
raise exc.NoNetworkAvailable() raise exc.NoNetworkAvailable()
else: else:
@ -297,8 +386,9 @@ class ML2TunnelTypeDriver(_TunnelTypeDriverBase):
api.SEGMENTATION_ID: getattr(alloc, self.segmentation_key), api.SEGMENTATION_ID: getattr(alloc, self.segmentation_key),
api.MTU: self.get_mtu()} api.MTU: self.get_mtu()}
def allocate_tenant_segment(self, context): def allocate_tenant_segment(self, context, filters=None):
alloc = self.allocate_partially_specified_segment(context) filters = filters or {}
alloc = self.allocate_partially_specified_segment(context, **filters)
if not alloc: if not alloc:
return return
return {api.NETWORK_TYPE: self.get_type(), return {api.NETWORK_TYPE: self.get_type(),
@ -309,7 +399,8 @@ class ML2TunnelTypeDriver(_TunnelTypeDriverBase):
def release_segment(self, context, segment): def release_segment(self, context, segment):
tunnel_id = segment[api.SEGMENTATION_ID] tunnel_id = segment[api.SEGMENTATION_ID]
inside = any(lo <= tunnel_id <= hi for lo, hi in self.tunnel_ranges) ranges = self.get_network_segment_ranges()
inside = any(lo <= tunnel_id <= hi for lo, hi in ranges)
info = {'type': self.get_type(), 'id': tunnel_id} info = {'type': self.get_type(), 'id': tunnel_id}
with db_api.CONTEXT_WRITER.using(context): with db_api.CONTEXT_WRITER.using(context):

View File

@ -19,16 +19,22 @@ from neutron_lib import constants as p_const
from neutron_lib import context from neutron_lib import context
from neutron_lib.db import api as db_api from neutron_lib.db import api as db_api
from neutron_lib import exceptions as exc from neutron_lib import exceptions as exc
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.ml2 import api
from neutron_lib.plugins import utils as plugin_utils from neutron_lib.plugins import utils as plugin_utils
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log from oslo_log import log
from oslo_utils import uuidutils
from six import moves from six import moves
from neutron._i18n import _ from neutron._i18n import _
from neutron.conf.plugins.ml2.drivers import driver_type from neutron.conf.plugins.ml2.drivers import driver_type
from neutron.db.models.plugins.ml2 import vlanallocation as vlan_alloc_model
from neutron.objects import network_segment_range as range_obj
from neutron.objects.plugins.ml2 import vlanallocation as vlanalloc from neutron.objects.plugins.ml2 import vlanallocation as vlanalloc
from neutron.plugins.ml2.drivers import helpers from neutron.plugins.ml2.drivers import helpers
from neutron.services.network_segment_range import plugin as range_plugin
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -48,8 +54,42 @@ class VlanTypeDriver(helpers.SegmentTypeDriver):
def __init__(self): def __init__(self):
super(VlanTypeDriver, self).__init__(vlanalloc.VlanAllocation) super(VlanTypeDriver, self).__init__(vlanalloc.VlanAllocation)
self.model_segmentation_id = vlan_alloc_model.VlanAllocation.vlan_id
self._parse_network_vlan_ranges() self._parse_network_vlan_ranges()
@db_api.retry_db_errors
def _populate_new_default_network_segment_ranges(self):
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()
@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': p_const.TYPE_VLAN,
}
old_default_range_objs = range_obj.NetworkSegmentRange.get_objects(
ctx, **filters)
for obj in old_default_range_objs:
obj.delete()
def _parse_network_vlan_ranges(self): def _parse_network_vlan_ranges(self):
try: try:
self.network_vlan_ranges = plugin_utils.parse_network_vlan_ranges( self.network_vlan_ranges = plugin_utils.parse_network_vlan_ranges(
@ -73,8 +113,9 @@ class VlanTypeDriver(helpers.SegmentTypeDriver):
allocations[alloc.physical_network].append(alloc) allocations[alloc.physical_network].append(alloc)
# process vlan ranges for each configured physical network # process vlan ranges for each configured physical network
ranges = self.get_network_segment_ranges()
for (physical_network, for (physical_network,
vlan_ranges) in self.network_vlan_ranges.items(): vlan_ranges) in ranges.items():
# determine current configured allocatable vlans for # determine current configured allocatable vlans for
# this physical network # this physical network
vlan_ids = set() vlan_ids = set()
@ -132,21 +173,71 @@ class VlanTypeDriver(helpers.SegmentTypeDriver):
alloc.physical_network}) alloc.physical_network})
alloc.delete() alloc.delete()
@db_api.retry_db_errors
def _get_network_segment_ranges_from_db(self):
ranges = {}
ctx = context.get_admin_context()
with db_api.CONTEXT_READER.using(ctx):
range_objs = (range_obj.NetworkSegmentRange.get_objects(
ctx, network_type=self.get_type()))
for obj in range_objs:
physical_network = obj['physical_network']
if physical_network not in ranges:
ranges[physical_network] = []
ranges[physical_network].append((obj['minimum'],
obj['maximum']))
return ranges
def get_type(self): def get_type(self):
return p_const.TYPE_VLAN return p_const.TYPE_VLAN
def initialize(self): def initialize(self):
if not range_plugin.is_network_segment_range_enabled():
# service plugins are initialized/loaded after the ML2 driver
# initialization. Thus, we base on the information whether
# ``network_segment_range`` service plugin is enabled/defined in
# ``neutron.conf`` to decide whether to skip the first time sync
# allocation during driver initialization, instead of using the
# directory.get_plugin() method - the normal way used elsewhere to
# check if a plugin is loaded.
self._sync_vlan_allocations() self._sync_vlan_allocations()
LOG.info("VlanTypeDriver initialization complete") 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()
# 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
# initialization time.
self.network_vlan_ranges = self._get_network_segment_ranges_from_db()
self._sync_vlan_allocations()
def update_network_segment_range_allocations(self):
self._sync_vlan_allocations()
def get_network_segment_ranges(self):
"""Get the driver network segment ranges.
Queries all VLAN network segment ranges from DB if the
``NETWORK_SEGMENT_RANGE`` service plugin is enabled. Otherwise,
they will be loaded from the host config file - `ml2_conf.ini`.
"""
ranges = self.network_vlan_ranges
if directory.get_plugin(plugin_constants.NETWORK_SEGMENT_RANGE):
ranges = self._get_network_segment_ranges_from_db()
return ranges
def is_partial_segment(self, segment): def is_partial_segment(self, segment):
return segment.get(api.SEGMENTATION_ID) is None return segment.get(api.SEGMENTATION_ID) is None
def validate_provider_segment(self, segment): def validate_provider_segment(self, segment):
physical_network = segment.get(api.PHYSICAL_NETWORK) physical_network = segment.get(api.PHYSICAL_NETWORK)
segmentation_id = segment.get(api.SEGMENTATION_ID) segmentation_id = segment.get(api.SEGMENTATION_ID)
ranges = self.get_network_segment_ranges()
if physical_network: if physical_network:
if physical_network not in self.network_vlan_ranges: if physical_network not in ranges:
msg = (_("physical_network '%s' unknown " msg = (_("physical_network '%s' unknown "
"for VLAN provider network") % physical_network) "for VLAN provider network") % physical_network)
raise exc.InvalidInput(error_message=msg) raise exc.InvalidInput(error_message=msg)
@ -158,7 +249,7 @@ class VlanTypeDriver(helpers.SegmentTypeDriver):
'max': p_const.MAX_VLAN_TAG}) 'max': p_const.MAX_VLAN_TAG})
raise exc.InvalidInput(error_message=msg) raise exc.InvalidInput(error_message=msg)
else: else:
if not self.network_vlan_ranges.get(physical_network): if not ranges.get(physical_network):
msg = (_("Physical network %s requires segmentation_id " msg = (_("Physical network %s requires segmentation_id "
"to be specified when creating a provider " "to be specified when creating a provider "
"network") % physical_network) "network") % physical_network)
@ -175,7 +266,9 @@ class VlanTypeDriver(helpers.SegmentTypeDriver):
msg = _("%s prohibited for VLAN provider network") % key msg = _("%s prohibited for VLAN provider network") % key
raise exc.InvalidInput(error_message=msg) raise exc.InvalidInput(error_message=msg)
def reserve_provider_segment(self, context, segment): def reserve_provider_segment(self, context, segment, filters=None):
filters = filters or {}
project_id = filters.get('project_id')
filters = {} filters = {}
physical_network = segment.get(api.PHYSICAL_NETWORK) physical_network = segment.get(api.PHYSICAL_NETWORK)
if physical_network is not None: if physical_network is not None:
@ -185,6 +278,9 @@ class VlanTypeDriver(helpers.SegmentTypeDriver):
filters['vlan_id'] = vlan_id filters['vlan_id'] = vlan_id
if self.is_partial_segment(segment): if self.is_partial_segment(segment):
if (directory.get_plugin(
plugin_constants.NETWORK_SEGMENT_RANGE)and project_id):
filters['project_id'] = project_id
alloc = self.allocate_partially_specified_segment( alloc = self.allocate_partially_specified_segment(
context, **filters) context, **filters)
if not alloc: if not alloc:
@ -200,10 +296,13 @@ class VlanTypeDriver(helpers.SegmentTypeDriver):
api.SEGMENTATION_ID: alloc.vlan_id, api.SEGMENTATION_ID: alloc.vlan_id,
api.MTU: self.get_mtu(alloc.physical_network)} api.MTU: self.get_mtu(alloc.physical_network)}
def allocate_tenant_segment(self, context): def allocate_tenant_segment(self, context, filters=None):
for physnet in self.network_vlan_ranges: filters = filters or {}
ranges = self.get_network_segment_ranges()
for physnet in ranges:
filters['physical_network'] = physnet
alloc = self.allocate_partially_specified_segment( alloc = self.allocate_partially_specified_segment(
context, physical_network=physnet) context, **filters)
if alloc: if alloc:
break break
else: else:
@ -217,7 +316,8 @@ class VlanTypeDriver(helpers.SegmentTypeDriver):
physical_network = segment[api.PHYSICAL_NETWORK] physical_network = segment[api.PHYSICAL_NETWORK]
vlan_id = segment[api.SEGMENTATION_ID] vlan_id = segment[api.SEGMENTATION_ID]
ranges = self.network_vlan_ranges.get(physical_network, []) vlan_ranges = self.get_network_segment_ranges()
ranges = vlan_ranges.get(physical_network, [])
inside = any(lo <= vlan_id <= hi for lo, hi in ranges) inside = any(lo <= vlan_id <= hi for lo, hi in ranges)
count = False count = False

View File

@ -19,6 +19,7 @@ from oslo_config import cfg
from oslo_log import log from oslo_log import log
from neutron.conf.plugins.ml2.drivers import driver_type from neutron.conf.plugins.ml2.drivers import driver_type
from neutron.db.models.plugins.ml2 import vxlanallocation as vxlan_alloc_model
from neutron.objects.plugins.ml2 import vxlanallocation as vxlan_obj from neutron.objects.plugins.ml2 import vxlanallocation as vxlan_obj
from neutron.plugins.ml2.drivers import type_tunnel from neutron.plugins.ml2.drivers import type_tunnel
@ -32,6 +33,8 @@ class VxlanTypeDriver(type_tunnel.EndpointTunnelTypeDriver):
def __init__(self): def __init__(self):
super(VxlanTypeDriver, self).__init__( super(VxlanTypeDriver, self).__init__(
vxlan_obj.VxlanAllocation, vxlan_obj.VxlanEndpoint) vxlan_obj.VxlanAllocation, vxlan_obj.VxlanEndpoint)
self.model_segmentation_id = (
vxlan_alloc_model.VxlanAllocation.vxlan_vni)
def get_type(self): def get_type(self):
return p_const.TYPE_VXLAN return p_const.TYPE_VXLAN

View File

@ -187,6 +187,13 @@ class TypeManager(stevedore.named.NamedExtensionManager):
LOG.info("Initializing driver for type '%s'", network_type) LOG.info("Initializing driver for type '%s'", network_type)
driver.obj.initialize() driver.obj.initialize()
def initialize_network_segment_range_support(self):
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()
def _add_network_segment(self, context, network_id, segment, def _add_network_segment(self, context, network_id, segment,
segment_index=0): segment_index=0):
segments_db.add_network_segment( segments_db.add_network_segment(
@ -195,20 +202,23 @@ class TypeManager(stevedore.named.NamedExtensionManager):
def create_network_segments(self, context, network, tenant_id): def create_network_segments(self, context, network, tenant_id):
"""Call type drivers to create network segments.""" """Call type drivers to create network segments."""
segments = self._process_provider_create(network) segments = self._process_provider_create(network)
filters = {'project_id': tenant_id}
with db_api.CONTEXT_WRITER.using(context): with db_api.CONTEXT_WRITER.using(context):
network_id = network['id'] network_id = network['id']
if segments: if segments:
for segment_index, segment in enumerate(segments): for segment_index, segment in enumerate(segments):
segment = self.reserve_provider_segment( segment = self.reserve_provider_segment(
context, segment) context, segment, filters=filters)
self._add_network_segment(context, network_id, segment, self._add_network_segment(context, network_id, segment,
segment_index) segment_index)
elif (cfg.CONF.ml2.external_network_type and elif (cfg.CONF.ml2.external_network_type and
self._get_attribute(network, extnet_apidef.EXTERNAL)): self._get_attribute(network, extnet_apidef.EXTERNAL)):
segment = self._allocate_ext_net_segment(context) segment = self._allocate_ext_net_segment(
context, filters=filters)
self._add_network_segment(context, network_id, segment) self._add_network_segment(context, network_id, segment)
else: else:
segment = self._allocate_tenant_net_segment(context) segment = self._allocate_tenant_net_segment(
context, filters=filters)
self._add_network_segment(context, network_id, segment) self._add_network_segment(context, network_id, segment)
def reserve_network_segment(self, context, segment_data): def reserve_network_segment(self, context, segment_data):
@ -249,33 +259,33 @@ class TypeManager(stevedore.named.NamedExtensionManager):
msg = _("network_type value '%s' not supported") % network_type msg = _("network_type value '%s' not supported") % network_type
raise exc.InvalidInput(error_message=msg) raise exc.InvalidInput(error_message=msg)
def reserve_provider_segment(self, context, segment): def reserve_provider_segment(self, context, segment, filters=None):
network_type = segment.get(api.NETWORK_TYPE) network_type = segment.get(api.NETWORK_TYPE)
driver = self.drivers.get(network_type) driver = self.drivers.get(network_type)
if isinstance(driver.obj, api.TypeDriver): if isinstance(driver.obj, api.TypeDriver):
return driver.obj.reserve_provider_segment(context.session, return driver.obj.reserve_provider_segment(context.session,
segment) segment, filters)
else: else:
return driver.obj.reserve_provider_segment(context, return driver.obj.reserve_provider_segment(context,
segment) segment, filters)
def _allocate_segment(self, context, network_type): def _allocate_segment(self, context, network_type, filters=None):
driver = self.drivers.get(network_type) driver = self.drivers.get(network_type)
if isinstance(driver.obj, api.TypeDriver): if isinstance(driver.obj, api.TypeDriver):
return driver.obj.allocate_tenant_segment(context.session) return driver.obj.allocate_tenant_segment(context.session, filters)
else: else:
return driver.obj.allocate_tenant_segment(context) return driver.obj.allocate_tenant_segment(context, filters)
def _allocate_tenant_net_segment(self, context): def _allocate_tenant_net_segment(self, context, filters=None):
for network_type in self.tenant_network_types: for network_type in self.tenant_network_types:
segment = self._allocate_segment(context, network_type) segment = self._allocate_segment(context, network_type, filters)
if segment: if segment:
return segment return segment
raise exc.NoNetworkAvailable() raise exc.NoNetworkAvailable()
def _allocate_ext_net_segment(self, context): def _allocate_ext_net_segment(self, context, filters=None):
network_type = cfg.CONF.ml2.external_network_type network_type = cfg.CONF.ml2.external_network_type
segment = self._allocate_segment(context, network_type) segment = self._allocate_segment(context, network_type, filters)
if segment: if segment:
return segment return segment
raise exc.NoNetworkAvailable() raise exc.NoNetworkAvailable()
@ -336,6 +346,13 @@ class TypeManager(stevedore.named.NamedExtensionManager):
else: else:
LOG.debug("No segment found with id %(segment_id)s", segment_id) LOG.debug("No segment found with id %(segment_id)s", segment_id)
def update_network_segment_range_allocations(self, network_type):
driver = self.drivers.get(network_type)
driver.obj.update_network_segment_range_allocations()
def network_type_supported(self, network_type):
return bool(network_type in self.drivers)
class MechanismManager(stevedore.named.NamedExtensionManager): class MechanismManager(stevedore.named.NamedExtensionManager):
"""Manage networking mechanisms using drivers.""" """Manage networking mechanisms using drivers."""

View File

@ -19,6 +19,7 @@ from neutron_lib import exceptions as lib_exc
from neutron_lib.exceptions import network_segment_range as range_exc from neutron_lib.exceptions import network_segment_range as range_exc
from neutron_lib.plugins import directory from neutron_lib.plugins import directory
from neutron_lib.plugins import utils as plugin_utils from neutron_lib.plugins import utils as plugin_utils
from oslo_config import cfg
from oslo_log import helpers as log_helpers from oslo_log import helpers as log_helpers
from oslo_log import log from oslo_log import log
import six import six
@ -32,6 +33,13 @@ from neutron.objects import network_segment_range as obj_network_segment_range
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
def is_network_segment_range_enabled():
network_segment_range_class = ('neutron.services.network_segment_range.'
'plugin.NetworkSegmentRangePlugin')
return any(p in cfg.CONF.service_plugins
for p in ['network_segment_range', network_segment_range_class])
class NetworkSegmentRangePlugin(ext_range.NetworkSegmentRangePluginBase): class NetworkSegmentRangePlugin(ext_range.NetworkSegmentRangePluginBase):
"""Implements Neutron Network Segment Range Service plugin.""" """Implements Neutron Network Segment Range Service plugin."""

View File

@ -23,6 +23,7 @@ from six import moves
import testtools import testtools
from testtools import matchers from testtools import matchers
from neutron.objects import network_segment_range as obj_network_segment_range
from neutron.plugins.ml2.drivers import type_tunnel from neutron.plugins.ml2.drivers import type_tunnel
TUNNEL_IP_ONE = "10.10.10.10" TUNNEL_IP_ONE = "10.10.10.10"
@ -32,8 +33,11 @@ HOST_ONE = 'fake_host_one'
HOST_TWO = 'fake_host_two' HOST_TWO = 'fake_host_two'
TUN_MIN = 100 TUN_MIN = 100
TUN_MAX = 109 TUN_MAX = 109
RAW_TUNNEL_RANGES = [str(TUN_MIN) + ':' + str(TUN_MAX)]
TUNNEL_RANGES = [(TUN_MIN, TUN_MAX)] TUNNEL_RANGES = [(TUN_MIN, TUN_MAX)]
UPDATED_TUNNEL_RANGES = [(TUN_MIN + 5, TUN_MAX + 5)] UPDATED_TUNNEL_RANGES = [(TUN_MIN + 5, TUN_MAX + 5)]
SERVICE_PLUGIN_KLASS = ('neutron.services.network_segment_range.plugin.'
'NetworkSegmentRangePlugin')
class TunnelTypeTestMixin(object): class TunnelTypeTestMixin(object):
@ -460,3 +464,42 @@ class TunnelTypeMTUTestMixin(object):
def test_get_mtu_ipv6(self): def test_get_mtu_ipv6(self):
self._test_get_mtu(6) self._test_get_mtu(6)
class TunnelTypeNetworkSegmentRangeTestMixin(object):
DRIVER_CLASS = None
def setUp(self):
super(TunnelTypeNetworkSegmentRangeTestMixin, self).setUp()
cfg.CONF.set_override('service_plugins', [SERVICE_PLUGIN_KLASS])
self.context = context.Context()
self.driver = self.DRIVER_CLASS()
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.sync_allocations()
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
self.context)
self.assertEqual(1, len(ret))
network_segment_range = ret[0]
self.assertTrue(network_segment_range.default)
self.assertTrue(network_segment_range.shared)
self.assertIsNone(network_segment_range.project_id)
self.assertEqual(self.driver.get_type(),
network_segment_range.network_type)
self.assertIsNone(network_segment_range.physical_network)
self.assertEqual(TUN_MIN, network_segment_range.minimum)
self.assertEqual(TUN_MAX, network_segment_range.maximum)
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()
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
self.context)
self.assertEqual(0, len(ret))

View File

@ -15,6 +15,8 @@
import mock import mock
from neutron_lib import context 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_db import exception as exc
from sqlalchemy.orm import query from sqlalchemy.orm import query
@ -29,6 +31,10 @@ VLAN_OUTSIDE = 100
NETWORK_VLAN_RANGES = { NETWORK_VLAN_RANGES = {
TENANT_NET: [(VLAN_MIN, VLAN_MAX)], TENANT_NET: [(VLAN_MIN, VLAN_MAX)],
} }
NETWORK_VLAN_RANGES_CFG_ENTRIES = [TENANT_NET, "%s:%s:%s" %
(TENANT_NET, VLAN_MIN, VLAN_MAX)]
SERVICE_PLUGIN_KLASS = ('neutron.services.network_segment_range.plugin.'
'NetworkSegmentRangePlugin')
class HelpersTest(testlib_api.SqlTestCase): class HelpersTest(testlib_api.SqlTestCase):
@ -131,3 +137,19 @@ class HelpersTest(testlib_api.SqlTestCase):
observed = self.driver.allocate_partially_specified_segment( observed = self.driver.allocate_partially_specified_segment(
self.context, **expected) self.context, **expected)
self.check_raw_segment(expected, observed) self.check_raw_segment(expected, observed)
class HelpersTestWithNetworkSegmentRange(HelpersTest):
def setUp(self):
super(HelpersTestWithNetworkSegmentRange, self).setUp()
cfg.CONF.set_override('network_vlan_ranges',
NETWORK_VLAN_RANGES_CFG_ENTRIES,
group='ml2_type_vlan')
cfg.CONF.set_override('service_plugins', [SERVICE_PLUGIN_KLASS])
self.network_vlan_ranges = plugin_utils.parse_network_vlan_ranges(
NETWORK_VLAN_RANGES_CFG_ENTRIES)
self.context = context.get_admin_context()
self.driver = type_vlan.VlanTypeDriver()
self.driver.initialize_network_segment_range_support()
self.driver._sync_vlan_allocations()

View File

@ -28,6 +28,7 @@ HOST_TWO = 'fake_host_two2'
class GeneveTypeTest(base_type_tunnel.TunnelTypeTestMixin, class GeneveTypeTest(base_type_tunnel.TunnelTypeTestMixin,
base_type_tunnel.TunnelTypeNetworkSegmentRangeTestMixin,
testlib_api.SqlTestCase): testlib_api.SqlTestCase):
DRIVER_CLASS = type_geneve.GeneveTypeDriver DRIVER_CLASS = type_geneve.GeneveTypeDriver
TYPE = p_const.TYPE_GENEVE TYPE = p_const.TYPE_GENEVE

View File

@ -28,6 +28,7 @@ HOST_TWO = 'fake_host_two'
class GreTypeTest(base_type_tunnel.TunnelTypeTestMixin, class GreTypeTest(base_type_tunnel.TunnelTypeTestMixin,
base_type_tunnel.TunnelTypeNetworkSegmentRangeTestMixin,
testlib_api.SqlTestCase): testlib_api.SqlTestCase):
DRIVER_MODULE = type_gre DRIVER_MODULE = type_gre
DRIVER_CLASS = type_gre.GreTypeDriver DRIVER_CLASS = type_gre.GreTypeDriver

View File

@ -23,6 +23,7 @@ from neutron_lib.plugins import utils as plugin_utils
from oslo_config import cfg from oslo_config import cfg
from testtools import matchers from testtools import matchers
from neutron.objects import network_segment_range as obj_network_segment_range
from neutron.objects.plugins.ml2 import vlanallocation as vlan_alloc_obj from neutron.objects.plugins.ml2 import vlanallocation as vlan_alloc_obj
from neutron.plugins.ml2.drivers import type_vlan from neutron.plugins.ml2.drivers import type_vlan
from neutron.tests.unit import testlib_api from neutron.tests.unit import testlib_api
@ -41,6 +42,8 @@ EMPTY_VLAN_RANGES = {
PROVIDER_NET: [] PROVIDER_NET: []
} }
CORE_PLUGIN = 'ml2' CORE_PLUGIN = 'ml2'
SERVICE_PLUGIN_KLASS = ('neutron.services.network_segment_range.plugin.'
'NetworkSegmentRangePlugin')
class VlanTypeTest(testlib_api.SqlTestCase): class VlanTypeTest(testlib_api.SqlTestCase):
@ -319,3 +322,41 @@ class VlanTypeAllocationTest(testlib_api.SqlTestCase):
driver.allocate_tenant_segment(ctx)) driver.allocate_tenant_segment(ctx))
# then nothing # then nothing
self.assertFalse(driver.allocate_tenant_segment(ctx)) self.assertFalse(driver.allocate_tenant_segment(ctx))
class VlanTypeTestWithNetworkSegmentRange(testlib_api.SqlTestCase):
def setUp(self):
super(VlanTypeTestWithNetworkSegmentRange, self).setUp()
cfg.CONF.set_override('network_vlan_ranges',
NETWORK_VLAN_RANGES,
group='ml2_type_vlan')
cfg.CONF.set_override('service_plugins', [SERVICE_PLUGIN_KLASS])
self.network_vlan_ranges = plugin_utils.parse_network_vlan_ranges(
NETWORK_VLAN_RANGES)
self.driver = type_vlan.VlanTypeDriver()
self.driver._sync_vlan_allocations()
self.context = context.Context()
self.setup_coreplugin(CORE_PLUGIN)
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`
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
self.context)
self.assertEqual(1, len(ret))
network_segment_range = ret[0]
self.assertTrue(network_segment_range.default)
self.assertTrue(network_segment_range.shared)
self.assertIsNone(network_segment_range.project_id)
self.assertEqual(p_const.TYPE_VLAN, network_segment_range.network_type)
self.assertEqual(TENANT_NET, network_segment_range.physical_network)
self.assertEqual(VLAN_MIN, network_segment_range.minimum)
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()
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
self.context)
self.assertEqual(0, len(ret))

View File

@ -26,6 +26,7 @@ VXLAN_UDP_PORT_TWO = 8888
class VxlanTypeTest(base_type_tunnel.TunnelTypeTestMixin, class VxlanTypeTest(base_type_tunnel.TunnelTypeTestMixin,
base_type_tunnel.TunnelTypeNetworkSegmentRangeTestMixin,
testlib_api.SqlTestCase): testlib_api.SqlTestCase):
DRIVER_MODULE = type_vxlan DRIVER_MODULE = type_vxlan
DRIVER_CLASS = type_vxlan.VxlanTypeDriver DRIVER_CLASS = type_vxlan.VxlanTypeDriver