Support Network Segment Range CRUD as extensions
This patch adds the support for network segment range CRUD. Subsequent patches will be added to use this network segment range on segment allocation if this extension is loaded. Changes include: - an API extension which exposes the segment range to be administered; - standard attributes with tagging support for the new resource; - a new service plugin "network_segment_range" for the feature enabling/disabling; - a new network segment range DB table model along with operation logic; - Oslo Versioned Objects for network segment range data model; - policy-in-code support for network segment range. Co-authored-by: Allain Legacy <Allain.legacy@windriver.com> Partially-implements: blueprint network-segment-range-management Change-Id: I75814e50b2c9402fe6776229d469745d7a72290b
This commit is contained in:
parent
513dd7f46b
commit
563a536d02
@ -22,6 +22,7 @@ This includes:
|
||||
* segments
|
||||
* policies
|
||||
* trunks
|
||||
* network_segment_ranges
|
||||
|
||||
Use cases
|
||||
~~~~~~~~~
|
||||
|
@ -76,3 +76,4 @@ Current API resources extended by standard attr extensions:
|
||||
- ports: neutron.db.models_v2.Port
|
||||
- security_groups: neutron.db.models.securitygroup.SecurityGroup
|
||||
- floatingips: neutron.db.l3_db.FloatingIP
|
||||
- network_segment_ranges: neutron.db.models.network_segment_range.NetworkSegmentRange
|
||||
|
@ -60,6 +60,7 @@ Current API resources extended by tag extensions:
|
||||
|
||||
- floatingips
|
||||
- networks
|
||||
- network_segment_ranges
|
||||
- policies
|
||||
- ports
|
||||
- routers
|
||||
|
@ -28,6 +28,7 @@ from neutron.conf.policies import logging
|
||||
from neutron.conf.policies import metering
|
||||
from neutron.conf.policies import network
|
||||
from neutron.conf.policies import network_ip_availability
|
||||
from neutron.conf.policies import network_segment_range
|
||||
from neutron.conf.policies import port
|
||||
from neutron.conf.policies import qos
|
||||
from neutron.conf.policies import rbac
|
||||
@ -55,6 +56,7 @@ def list_rules():
|
||||
metering.list_rules(),
|
||||
network.list_rules(),
|
||||
network_ip_availability.list_rules(),
|
||||
network_segment_range.list_rules(),
|
||||
port.list_rules(),
|
||||
qos.list_rules(),
|
||||
rbac.list_rules(),
|
||||
|
78
neutron/conf/policies/network_segment_range.py
Normal file
78
neutron/conf/policies/network_segment_range.py
Normal file
@ -0,0 +1,78 @@
|
||||
# Copyright (c) 2019 Intel Corporation.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from oslo_policy import policy
|
||||
|
||||
from neutron.conf.policies import base
|
||||
|
||||
|
||||
COLLECTION_PATH = '/network_segment_ranges'
|
||||
RESOURCE_PATH = '/network_segment_ranges/{id}'
|
||||
|
||||
|
||||
rules = [
|
||||
policy.DocumentedRuleDefault(
|
||||
'create_network_segment_range',
|
||||
base.RULE_ADMIN_ONLY,
|
||||
'Create a network segment range',
|
||||
[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': COLLECTION_PATH,
|
||||
},
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
'get_network_segment_range',
|
||||
base.RULE_ADMIN_ONLY,
|
||||
'Get a network segment range',
|
||||
[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': COLLECTION_PATH,
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': RESOURCE_PATH,
|
||||
},
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
'update_network_segment_range',
|
||||
base.RULE_ADMIN_ONLY,
|
||||
'Update a network segment range',
|
||||
[
|
||||
{
|
||||
'method': 'PUT',
|
||||
'path': RESOURCE_PATH,
|
||||
},
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
'delete_network_segment_range',
|
||||
base.RULE_ADMIN_ONLY,
|
||||
'Delete a network segment range',
|
||||
[
|
||||
{
|
||||
'method': 'DELETE',
|
||||
'path': RESOURCE_PATH,
|
||||
},
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return rules
|
@ -1 +1 @@
|
||||
fb0167bd9639
|
||||
0ff9e3881597
|
||||
|
@ -0,0 +1,56 @@
|
||||
# Copyright 2019 OpenStack Foundation
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
"""description of revision
|
||||
|
||||
Revision ID: 0ff9e3881597
|
||||
Revises: fb0167bd9639
|
||||
Create Date: 2019-02-27 14:40:15.492884
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0ff9e3881597'
|
||||
down_revision = 'fb0167bd9639'
|
||||
|
||||
network_segment_range_network_type = sa.Enum(
|
||||
'vlan', 'vxlan', 'gre', 'geneve',
|
||||
name='network_segment_range_network_type')
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
'network_segment_ranges',
|
||||
sa.Column('id', sa.String(length=36), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('default', sa.Boolean(), nullable=False),
|
||||
sa.Column('shared', sa.Boolean(), nullable=False),
|
||||
sa.Column('project_id', sa.String(length=255), nullable=True),
|
||||
sa.Column('network_type', network_segment_range_network_type,
|
||||
nullable=False),
|
||||
sa.Column('physical_network', sa.String(length=64), nullable=True),
|
||||
sa.Column('minimum', sa.Integer(), nullable=True),
|
||||
sa.Column('maximum', sa.Integer(), nullable=True),
|
||||
sa.Column('standard_attr_id', sa.BigInteger(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['standard_attr_id'],
|
||||
['standardattributes.id'],
|
||||
ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('standard_attr_id')
|
||||
)
|
79
neutron/db/models/network_segment_range.py
Normal file
79
neutron/db/models/network_segment_range.py
Normal file
@ -0,0 +1,79 @@
|
||||
# Copyright (c) 2019 Intel Corporation.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from neutron_lib.api.definitions import network_segment_range as range_apidef
|
||||
from neutron_lib import constants
|
||||
from neutron_lib.db import constants as db_const
|
||||
from neutron_lib.db import model_base
|
||||
import sqlalchemy as sa
|
||||
|
||||
from neutron.db import standard_attr
|
||||
|
||||
|
||||
class NetworkSegmentRange(standard_attr.HasStandardAttributes,
|
||||
model_base.BASEV2, model_base.HasId,
|
||||
model_base.HasProject):
|
||||
"""Represents network segment range data."""
|
||||
__tablename__ = 'network_segment_ranges'
|
||||
|
||||
# user-defined network segment range name
|
||||
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE), nullable=True)
|
||||
|
||||
# defines whether the network segment range is loaded from host config
|
||||
# files and used as the default range when there is no other available
|
||||
default = sa.Column(sa.Boolean, default=False, nullable=False)
|
||||
|
||||
# defines whether multiple tenants can use this network segment range
|
||||
shared = sa.Column(sa.Boolean, default=True, nullable=False)
|
||||
|
||||
# the project_id is the subject that the policy will affect. this may
|
||||
# also be a wildcard '*' to indicate all tenants or it may be a role if
|
||||
# neutron gets better integration with keystone
|
||||
project_id = sa.Column(sa.String(db_const.PROJECT_ID_FIELD_SIZE),
|
||||
nullable=True)
|
||||
|
||||
# network segment range network type
|
||||
network_type = sa.Column(sa.Enum(
|
||||
constants.TYPE_VLAN,
|
||||
constants.TYPE_VXLAN,
|
||||
constants.TYPE_GRE,
|
||||
constants.TYPE_GENEVE,
|
||||
name='network_segment_range_network_type'),
|
||||
nullable=False)
|
||||
|
||||
# network segment range physical network, only applicable for VLAN.
|
||||
physical_network = sa.Column(sa.String(64))
|
||||
|
||||
# minimum segmentation id value
|
||||
minimum = sa.Column(sa.Integer)
|
||||
|
||||
# maximum segmentation id value
|
||||
maximum = sa.Column(sa.Integer)
|
||||
|
||||
api_collections = [range_apidef.COLLECTION_NAME]
|
||||
collection_resource_map = {
|
||||
range_apidef.COLLECTION_NAME: range_apidef.RESOURCE_NAME}
|
||||
tag_support = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NetworkSegmentRange, self).__init__(*args, **kwargs)
|
||||
self.project_id = None if self.shared else kwargs['project_id']
|
||||
is_vlan = self.network_type == constants.TYPE_VLAN
|
||||
self.physical_network = kwargs['physical_network'] if is_vlan else None
|
||||
|
||||
def __repr__(self):
|
||||
return "<NetworkSegmentRange(%s,%s,%s,%s,%s,%s - %s,%s)>" % (
|
||||
self.id, self.name, str(self.shared), self.project_id,
|
||||
self.network_type, self.physical_network, self.minimum,
|
||||
self.maximum)
|
@ -25,6 +25,10 @@ class GeneveAllocation(model_base.BASEV2):
|
||||
allocated = sa.Column(sa.Boolean, nullable=False, default=False,
|
||||
server_default=sql.false(), index=True)
|
||||
|
||||
@classmethod
|
||||
def get_segmentation_id(cls):
|
||||
return cls.geneve_vni
|
||||
|
||||
|
||||
class GeneveEndpoints(model_base.BASEV2):
|
||||
"""Represents tunnel endpoint in RPC mode."""
|
||||
|
@ -27,6 +27,10 @@ class GreAllocation(model_base.BASEV2):
|
||||
allocated = sa.Column(sa.Boolean, nullable=False, default=False,
|
||||
server_default=sql.false(), index=True)
|
||||
|
||||
@classmethod
|
||||
def get_segmentation_id(cls):
|
||||
return cls.gre_id
|
||||
|
||||
|
||||
class GreEndpoints(model_base.BASEV2):
|
||||
"""Represents tunnel endpoint in RPC mode."""
|
||||
|
@ -39,3 +39,7 @@ class VlanAllocation(model_base.BASEV2):
|
||||
vlan_id = sa.Column(sa.Integer, nullable=False, primary_key=True,
|
||||
autoincrement=False)
|
||||
allocated = sa.Column(sa.Boolean, nullable=False)
|
||||
|
||||
@classmethod
|
||||
def get_segmentation_id(cls):
|
||||
return cls.vlan_id
|
||||
|
@ -27,6 +27,10 @@ class VxlanAllocation(model_base.BASEV2):
|
||||
allocated = sa.Column(sa.Boolean, nullable=False, default=False,
|
||||
server_default=sql.false(), index=True)
|
||||
|
||||
@classmethod
|
||||
def get_segmentation_id(cls):
|
||||
return cls.vxlan_vni
|
||||
|
||||
|
||||
class VxlanEndpoints(model_base.BASEV2):
|
||||
"""Represents tunnel endpoint in RPC mode."""
|
||||
|
@ -122,3 +122,57 @@ def delete_network_segment(context, segment_id):
|
||||
"""Release a dynamic segment for the params provided if one exists."""
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
network_obj.NetworkSegment.delete_objects(context, id=segment_id)
|
||||
|
||||
|
||||
def network_segments_exist_in_range(context, network_type, physical_network,
|
||||
segment_range=None):
|
||||
"""Check whether one or more network segments exist in a range."""
|
||||
with db_api.CONTEXT_READER.using(context):
|
||||
filters = {
|
||||
'network_type': network_type,
|
||||
'physical_network': physical_network,
|
||||
}
|
||||
segment_objs = network_obj.NetworkSegment.get_objects(
|
||||
context, **filters)
|
||||
if segment_range:
|
||||
minimum_id = segment_range['minimum']
|
||||
maximum_id = segment_range['maximum']
|
||||
segment_objs = [
|
||||
segment for segment in segment_objs if
|
||||
minimum_id <= segment.segmentation_id <= maximum_id]
|
||||
return len(segment_objs) > 0
|
||||
|
||||
|
||||
def min_max_actual_segments_in_range(context, network_type, physical_network,
|
||||
segment_range=None):
|
||||
"""Return the minimum and maximum segmentation IDs used in a network
|
||||
segment range
|
||||
"""
|
||||
with db_api.CONTEXT_READER.using(context):
|
||||
filters = {
|
||||
'network_type': network_type,
|
||||
'physical_network': physical_network,
|
||||
}
|
||||
pager = base_obj.Pager()
|
||||
# (NOTE) True means ASC, False is DESC
|
||||
pager.sorts = [('segmentation_id', True)]
|
||||
segment_objs = network_obj.NetworkSegment.get_objects(
|
||||
context, _pager=pager, **filters)
|
||||
|
||||
if segment_range:
|
||||
minimum_id = segment_range['minimum']
|
||||
maximum_id = segment_range['maximum']
|
||||
segment_objs = [
|
||||
segment for segment in segment_objs if
|
||||
minimum_id <= segment.segmentation_id <= maximum_id]
|
||||
|
||||
if segment_objs:
|
||||
return (segment_objs[0].segmentation_id,
|
||||
segment_objs[-1].segmentation_id)
|
||||
else:
|
||||
LOG.debug("No existing segment found for "
|
||||
"Network type:%(network_type)s, "
|
||||
"Physical network:%(physical_network)s",
|
||||
{'network_type': network_type,
|
||||
'physical_network': physical_network})
|
||||
return None, None
|
||||
|
165
neutron/extensions/network_segment_range.py
Normal file
165
neutron/extensions/network_segment_range.py
Normal file
@ -0,0 +1,165 @@
|
||||
# Copyright (c) 2019 Intel Corporation.
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
from neutron_lib.api.definitions import network_segment_range as apidef
|
||||
from neutron_lib.api import extensions as api_extensions
|
||||
from neutron_lib.plugins import constants as plugin_constants
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_lib.services import base as service_base
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
from neutron.api import extensions
|
||||
from neutron.api.v2 import base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Network_segment_range(api_extensions.APIExtensionDescriptor):
|
||||
"""Extension class supporting Network segment ranges.
|
||||
|
||||
This class is used by neutron's extension framework to make metadata
|
||||
about the network segment range extension available to clients.
|
||||
|
||||
With admin rights, one will be able to create, update, read and delete the
|
||||
values.
|
||||
"""
|
||||
|
||||
api_definition = apidef
|
||||
|
||||
@classmethod
|
||||
def get_resources(cls):
|
||||
"""Returns extension resources"""
|
||||
plugin = directory.get_plugin(plugin_constants.NETWORK_SEGMENT_RANGE)
|
||||
collection_name = apidef.COLLECTION_NAME.replace('_', '-')
|
||||
params = apidef.RESOURCE_ATTRIBUTE_MAP.get(apidef.COLLECTION_NAME,
|
||||
dict())
|
||||
controller = base.create_resource(collection_name,
|
||||
apidef.RESOURCE_NAME,
|
||||
plugin, params, allow_bulk=True,
|
||||
allow_pagination=True,
|
||||
allow_sorting=True)
|
||||
|
||||
ex = extensions.ResourceExtension(collection_name, controller,
|
||||
attr_map=params)
|
||||
|
||||
return [ex]
|
||||
|
||||
@classmethod
|
||||
def get_plugin_interface(cls):
|
||||
return NetworkSegmentRangePluginBase
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class NetworkSegmentRangePluginBase(service_base.ServicePluginBase):
|
||||
"""REST API to manage network segment ranges.
|
||||
|
||||
All methods must be in an admin context.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_plugin_type(cls):
|
||||
return plugin_constants.NETWORK_SEGMENT_RANGE
|
||||
|
||||
def get_plugin_description(self):
|
||||
return "Adds network segment ranges to Neutron resources"
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_network_segment_range(self, context, network_segment_range):
|
||||
"""Create a network segment range.
|
||||
|
||||
Create a network segment range, which represents the range of L2
|
||||
segments for tenant network allocation.
|
||||
|
||||
:param context: neutron api request context
|
||||
:param network_segment_range: dictionary describing the network segment
|
||||
range, with keys as listed in the :obj:`RESOURCE_ATTRIBUTE_MAP`
|
||||
object in
|
||||
:file:`neutron_lib/api/definitions/network_segment_range.py`.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_network_segment_range(self, context, id):
|
||||
"""Delete a network segment range.
|
||||
|
||||
:param context: neutron api request context
|
||||
:param id: UUID representing the network segment range to delete.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_network_segment_range(self, context, id, network_segment_range):
|
||||
"""Update values of a network segment range.
|
||||
|
||||
:param context: neutron api request context
|
||||
:param id: UUID representing the network segment range to update.
|
||||
:param network_segment_range: dictionary with keys indicating fields to
|
||||
update. valid keys are those that have a value of True for
|
||||
'allow_put' as listed in the :obj:`RESOURCE_ATTRIBUTE_MAP`
|
||||
object in
|
||||
:file:`neutron_lib/api/definitions/network_segment_range.py`.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_network_segment_ranges(self, context, filters=None, fields=None,
|
||||
sorts=None, limit=None, marker=None,
|
||||
page_reverse=False):
|
||||
"""Retrieve a list of network segment ranges.
|
||||
|
||||
The contents of the list depends on the filters.
|
||||
|
||||
:param context: neutron api request context
|
||||
:param filters: a dictionary with keys that are valid keys for
|
||||
a network segment range as listed in the
|
||||
:obj:`RESOURCE_ATTRIBUTE_MAP` object in
|
||||
:file:`neutron_lib/api/definitions/
|
||||
network_segment_range.py`.
|
||||
Values in this dictionary are an iterable containing
|
||||
values that will be used for an exact match
|
||||
comparison for that value. Each result returned by
|
||||
this function will have matched one of the values
|
||||
for each key in filters.
|
||||
:param fields: a list of strings that are valid keys in a
|
||||
network segment range dictionary as listed in the
|
||||
:obj:`RESOURCE_ATTRIBUTE_MAP` object in
|
||||
:file:`neutron_lib/api/definitions/
|
||||
network_segment_range.py`.
|
||||
Only these fields will be returned.
|
||||
:param sorts: A list of (key, direction) tuples.
|
||||
direction: True == ASC, False == DESC
|
||||
:param limit: maximum number of items to return
|
||||
:param marker: the last item of the previous page; when used, returns
|
||||
next results after the marker resource.
|
||||
:param page_reverse: True if sort direction is reversed.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_network_segment_range(self, context, id, fields=None):
|
||||
"""Retrieve a network segment range.
|
||||
|
||||
:param context: neutron api request context
|
||||
:param id: UUID representing the network segment range to fetch.
|
||||
:param fields: a list of strings that are valid keys in a
|
||||
network segment range dictionary as listed in the
|
||||
:obj:`RESOURCE_ATTRIBUTE_MAP` object in
|
||||
:file:`neutron_lib/api/definitions/
|
||||
network_segment_range.py`.
|
||||
Only these fields will be returned.
|
||||
"""
|
||||
pass
|
@ -313,3 +313,8 @@ class FloatingIPStatusEnumField(obj_fields.AutoTypedField):
|
||||
|
||||
class RouterStatusEnumField(obj_fields.AutoTypedField):
|
||||
AUTO_TYPE = obj_fields.Enum(valid_values=constants.VALID_ROUTER_STATUS)
|
||||
|
||||
|
||||
class NetworkSegmentRangeNetworkTypeEnumField(obj_fields.AutoTypedField):
|
||||
AUTO_TYPE = obj_fields.Enum(
|
||||
valid_values=lib_constants.NETWORK_SEGMENT_RANGE_TYPES)
|
||||
|
127
neutron/objects/network_segment_range.py
Normal file
127
neutron/objects/network_segment_range.py
Normal file
@ -0,0 +1,127 @@
|
||||
# Copyright (c) 2019 Intel Corporation.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from neutron_lib import constants
|
||||
from neutron_lib.db import utils as db_utils
|
||||
from neutron_lib import exceptions as n_exc
|
||||
from oslo_versionedobjects import fields as obj_fields
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy import not_
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.db.models import network_segment_range as range_model
|
||||
from neutron.db.models.plugins.ml2 import geneveallocation as \
|
||||
geneve_alloc_model
|
||||
from neutron.db.models.plugins.ml2 import gre_allocation_endpoints as \
|
||||
gre_alloc_model
|
||||
from neutron.db.models.plugins.ml2 import vlanallocation as vlan_alloc_model
|
||||
from neutron.db.models.plugins.ml2 import vxlanallocation as vxlan_alloc_model
|
||||
from neutron.db.models import segment as segments_model
|
||||
from neutron.db import models_v2
|
||||
from neutron.objects import base
|
||||
from neutron.objects import common_types
|
||||
|
||||
|
||||
models_map = {
|
||||
constants.TYPE_VLAN: vlan_alloc_model.VlanAllocation,
|
||||
constants.TYPE_VXLAN: vxlan_alloc_model.VxlanAllocation,
|
||||
constants.TYPE_GRE: gre_alloc_model.GreAllocation,
|
||||
constants.TYPE_GENEVE: geneve_alloc_model.GeneveAllocation
|
||||
}
|
||||
|
||||
|
||||
@base.NeutronObjectRegistry.register
|
||||
class NetworkSegmentRange(base.NeutronDbObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
db_model = range_model.NetworkSegmentRange
|
||||
|
||||
primary_keys = ['id']
|
||||
|
||||
fields = {
|
||||
'id': common_types.UUIDField(),
|
||||
'name': obj_fields.StringField(nullable=True),
|
||||
'default': obj_fields.BooleanField(nullable=False),
|
||||
'shared': obj_fields.BooleanField(nullable=False),
|
||||
'project_id': obj_fields.StringField(nullable=True),
|
||||
'network_type': common_types.NetworkSegmentRangeNetworkTypeEnumField(
|
||||
nullable=False),
|
||||
'physical_network': obj_fields.StringField(nullable=True),
|
||||
'minimum': obj_fields.IntegerField(nullable=True),
|
||||
'maximum': obj_fields.IntegerField(nullable=True)
|
||||
}
|
||||
|
||||
def to_dict(self, fields=None):
|
||||
_dict = super(NetworkSegmentRange, self).to_dict()
|
||||
# extend the network segment range dict with `available` and `used`
|
||||
# fields
|
||||
_dict.update({'available': self._get_available_allocation()})
|
||||
_dict.update({'used': self._get_used_allocation_mapping()})
|
||||
# TODO(kailun): For tag mechanism. This will be removed in bug/1704137
|
||||
try:
|
||||
_dict['tags'] = [t.tag for t in self.db_obj.standard_attr.tags]
|
||||
except AttributeError:
|
||||
# AttrtibuteError can be raised when accessing self.db_obj
|
||||
# or self.db_obj.standard_attr
|
||||
pass
|
||||
return db_utils.resource_fields(_dict, fields)
|
||||
|
||||
def _get_allocation_model_details(self):
|
||||
model = models_map.get(self.network_type)
|
||||
if model is not None:
|
||||
alloc_segmentation_id = model.get_segmentation_id()
|
||||
else:
|
||||
msg = (_("network_type '%s' unknown for getting allocation "
|
||||
"information") % self.network_type)
|
||||
raise n_exc.InvalidInput(error_message=msg)
|
||||
allocated = model.allocated
|
||||
|
||||
return model, alloc_segmentation_id, allocated
|
||||
|
||||
def _get_available_allocation(self):
|
||||
with self.db_context_reader(self.obj_context):
|
||||
model, alloc_segmentation_id, allocated = (
|
||||
self._get_allocation_model_details())
|
||||
|
||||
query = self.obj_context.session.query(alloc_segmentation_id)
|
||||
query = query.filter(and_(
|
||||
alloc_segmentation_id >= self.minimum,
|
||||
alloc_segmentation_id <= self.maximum),
|
||||
not_(allocated))
|
||||
if self.network_type == constants.TYPE_VLAN:
|
||||
alloc_available = query.filter(
|
||||
model.physical_network == self.physical_network).all()
|
||||
else:
|
||||
alloc_available = query.all()
|
||||
|
||||
return [segmentation_id for (segmentation_id,) in alloc_available]
|
||||
|
||||
def _get_used_allocation_mapping(self):
|
||||
with self.db_context_reader(self.obj_context):
|
||||
query = self.obj_context.session.query(
|
||||
segments_model.NetworkSegment.segmentation_id,
|
||||
models_v2.Network.project_id)
|
||||
alloc_used = (query.filter(and_(
|
||||
segments_model.NetworkSegment.network_type ==
|
||||
self.network_type,
|
||||
segments_model.NetworkSegment.physical_network ==
|
||||
self.physical_network,
|
||||
segments_model.NetworkSegment.segmentation_id >= self.minimum,
|
||||
segments_model.NetworkSegment.segmentation_id <= self.maximum))
|
||||
.filter(
|
||||
segments_model.NetworkSegment.network_id ==
|
||||
models_v2.Network.id)).all()
|
||||
return {segmentation_id: project_id
|
||||
for segmentation_id, project_id in alloc_used}
|
0
neutron/services/network_segment_range/__init__.py
Normal file
0
neutron/services/network_segment_range/__init__.py
Normal file
266
neutron/services/network_segment_range/plugin.py
Normal file
266
neutron/services/network_segment_range/plugin.py
Normal file
@ -0,0 +1,266 @@
|
||||
# Copyright (c) 2019 Intel Corporation.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from neutron_lib.api.definitions import network_segment_range as range_def
|
||||
from neutron_lib import constants as const
|
||||
from neutron_lib.db import api as db_api
|
||||
from neutron_lib import exceptions as lib_exc
|
||||
from neutron_lib.exceptions import network_segment_range as range_exc
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_lib.plugins import utils as plugin_utils
|
||||
from oslo_log import helpers as log_helpers
|
||||
from oslo_log import log
|
||||
import six
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.db import segments_db
|
||||
from neutron.extensions import network_segment_range as ext_range
|
||||
from neutron.objects import base as base_obj
|
||||
from neutron.objects import network_segment_range as obj_network_segment_range
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class NetworkSegmentRangePlugin(ext_range.NetworkSegmentRangePluginBase):
|
||||
"""Implements Neutron Network Segment Range Service plugin."""
|
||||
|
||||
supported_extension_aliases = [range_def.ALIAS]
|
||||
|
||||
__native_pagination_support = True
|
||||
__native_sorting_support = True
|
||||
__filter_validation_support = True
|
||||
|
||||
def __init__(self):
|
||||
super(NetworkSegmentRangePlugin, self).__init__()
|
||||
self.type_manager = directory.get_plugin().type_manager
|
||||
self.type_manager.initialize_network_segment_range_support()
|
||||
|
||||
def _get_network_segment_range(self, context, id):
|
||||
obj = obj_network_segment_range.NetworkSegmentRange.get_object(
|
||||
context, id=id)
|
||||
if obj is None:
|
||||
raise range_exc.NetworkSegmentRangeNotFound(range_id=id)
|
||||
return obj
|
||||
|
||||
def _validate_network_segment_range_eligible(self, network_segment_range):
|
||||
range_data = (network_segment_range.get('minimum'),
|
||||
network_segment_range.get('maximum'))
|
||||
# Currently, network segment range only supports VLAN, VxLAN,
|
||||
# GRE and Geneve.
|
||||
if network_segment_range.get('network_type') == const.TYPE_VLAN:
|
||||
plugin_utils.verify_vlan_range(range_data)
|
||||
else:
|
||||
plugin_utils.verify_tunnel_range(
|
||||
range_data, network_segment_range.get('network_type'))
|
||||
|
||||
def _validate_network_segment_range_overlap(self, context,
|
||||
network_segment_range):
|
||||
filters = {
|
||||
'default': False,
|
||||
'network_type': network_segment_range['network_type'],
|
||||
'physical_network': (network_segment_range['physical_network']
|
||||
if network_segment_range['network_type'] ==
|
||||
const.TYPE_VLAN else None),
|
||||
}
|
||||
range_objs = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
||||
context, **filters)
|
||||
overlapped_range_id = [
|
||||
obj.id for obj in range_objs if
|
||||
(network_segment_range['minimum'] <= obj.maximum and
|
||||
network_segment_range['maximum'] >= obj.minimum)]
|
||||
if overlapped_range_id:
|
||||
raise range_exc.NetworkSegmentRangeOverlaps(
|
||||
range_id=', '.join(overlapped_range_id))
|
||||
|
||||
def _add_unchanged_range_attributes(self, updates, existing):
|
||||
"""Adds data for unspecified fields on incoming update requests."""
|
||||
for key, value in six.iteritems(existing):
|
||||
updates.setdefault(key, value)
|
||||
return updates
|
||||
|
||||
def _is_network_segment_range_referenced(self, context,
|
||||
network_segment_range):
|
||||
return segments_db.network_segments_exist_in_range(
|
||||
context, network_segment_range['network_type'],
|
||||
network_segment_range.get('physical_network'),
|
||||
network_segment_range)
|
||||
|
||||
def _is_network_segment_range_type_supported(self, network_type):
|
||||
if not (self.type_manager.network_type_supported(network_type) and
|
||||
network_type in const.NETWORK_SEGMENT_RANGE_TYPES):
|
||||
# TODO(kailun): To use
|
||||
# range_exc.NetworkSegmentRangeNetTypeNotSupported when the
|
||||
# neutron-lib patch https://review.openstack.org/640777 is merged
|
||||
# and released.
|
||||
message = _("Network type %s does not support "
|
||||
"network segment ranges.") % network_type
|
||||
raise lib_exc.BadRequest(resource=range_def.RESOURCE_NAME,
|
||||
msg=message)
|
||||
|
||||
return True
|
||||
|
||||
def _are_allocated_segments_in_range_impacted(self, context,
|
||||
existing_range,
|
||||
updated_range):
|
||||
updated_range_min = updated_range.get('minimum',
|
||||
existing_range['minimum'])
|
||||
updated_range_max = updated_range.get('maximum',
|
||||
existing_range['maximum'])
|
||||
existing_range_min, existing_range_max = (
|
||||
segments_db.min_max_actual_segments_in_range(
|
||||
context, existing_range['network_type'],
|
||||
existing_range.get('physical_network'), existing_range))
|
||||
|
||||
if existing_range_min and existing_range_max:
|
||||
return bool(updated_range_min >= existing_range_min or
|
||||
updated_range_max <= existing_range_max)
|
||||
return False
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def create_network_segment_range(self, context, network_segment_range):
|
||||
"""Check network types supported on network segment range creation."""
|
||||
range_data = network_segment_range['network_segment_range']
|
||||
if self._is_network_segment_range_type_supported(
|
||||
range_data['network_type']):
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
self._validate_network_segment_range_eligible(range_data)
|
||||
self._validate_network_segment_range_overlap(context,
|
||||
range_data)
|
||||
network_segment_range = (
|
||||
obj_network_segment_range.NetworkSegmentRange(
|
||||
context, name=range_data['name'],
|
||||
description=range_data.get('description'),
|
||||
default=False,
|
||||
shared=range_data['shared'],
|
||||
project_id=(range_data['project_id']
|
||||
if not range_data['shared'] else None),
|
||||
network_type=range_data['network_type'],
|
||||
physical_network=(range_data['physical_network']
|
||||
if range_data['network_type'] ==
|
||||
const.TYPE_VLAN else None),
|
||||
minimum=range_data['minimum'],
|
||||
maximum=range_data['maximum'])
|
||||
)
|
||||
network_segment_range.create()
|
||||
|
||||
self.type_manager.update_network_segment_range_allocations(
|
||||
network_segment_range['network_type'])
|
||||
return network_segment_range.to_dict()
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def get_network_segment_range(self, context, id, fields=None):
|
||||
network_segment_range = self._get_network_segment_range(
|
||||
context, id)
|
||||
return network_segment_range.to_dict(fields=fields)
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def get_network_segment_ranges(self, context, filters=None, fields=None,
|
||||
sorts=None, limit=None, marker=None,
|
||||
page_reverse=False):
|
||||
# TODO(kailun): Based on the current spec:
|
||||
# https://review.openstack.org/599980, this method call may
|
||||
# possibly return a large amount of data since ``available``
|
||||
# segment list and ``used`` segment/project mapping will be also
|
||||
# returned and they can be large sometimes. Considering that this
|
||||
# API is admin-only and list operations won't be called often based
|
||||
# on the use cases, we'll keep this open for now and evaluate the
|
||||
# potential impacts. An alternative is to return the ``available``
|
||||
# and ``used`` segment number or percentage.
|
||||
pager = base_obj.Pager(sorts, limit, page_reverse, marker)
|
||||
filters = filters or {}
|
||||
network_segment_ranges = (
|
||||
obj_network_segment_range.NetworkSegmentRange.get_objects(
|
||||
context, _pager=pager, **filters))
|
||||
|
||||
return [
|
||||
network_segment_range.to_dict(fields=fields)
|
||||
for network_segment_range in network_segment_ranges
|
||||
]
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def update_network_segment_range(self, context, id, network_segment_range):
|
||||
"""Check existing network segment range impact on range updates."""
|
||||
updated_range_data = network_segment_range['network_segment_range']
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
network_segment_range = self._get_network_segment_range(context,
|
||||
id)
|
||||
existing_range_data = network_segment_range.to_dict()
|
||||
|
||||
if existing_range_data['default']:
|
||||
# TODO(kailun): To use
|
||||
# range_exc.NetworkSegmentRangeDefaultReadOnly when the
|
||||
# neutron-lib patch https://review.openstack.org/640777 is
|
||||
# merged and released.
|
||||
message = _("Network Segment Range %s is a "
|
||||
"default segment range which could not be "
|
||||
"updated or deleted.") % id
|
||||
raise lib_exc.BadRequest(resource=range_def.RESOURCE_NAME,
|
||||
msg=message)
|
||||
|
||||
if self._are_allocated_segments_in_range_impacted(
|
||||
context,
|
||||
existing_range=existing_range_data,
|
||||
updated_range=updated_range_data):
|
||||
# TODO(kailun): To use
|
||||
# range_exc.NetworkSegmentRangeReferencedByProject when the
|
||||
# neutron-lib patch https://review.openstack.org/640777 is
|
||||
# merged and released.
|
||||
message = _("Network Segment Range %s is referenced by "
|
||||
"one or more tenant networks.") % id
|
||||
raise lib_exc.InUse(resource=range_def.RESOURCE_NAME,
|
||||
msg=message)
|
||||
|
||||
new_range_data = self._add_unchanged_range_attributes(
|
||||
updated_range_data, existing_range_data)
|
||||
self._validate_network_segment_range_eligible(new_range_data)
|
||||
network_segment_range.update_fields(new_range_data)
|
||||
network_segment_range.update()
|
||||
|
||||
self.type_manager.update_network_segment_range_allocations(
|
||||
network_segment_range['network_type'])
|
||||
return network_segment_range.to_dict()
|
||||
|
||||
@log_helpers.log_method_call
|
||||
def delete_network_segment_range(self, context, id):
|
||||
"""Check segment reference on network segment range deletion."""
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
network_segment_range = self._get_network_segment_range(context,
|
||||
id)
|
||||
range_data = network_segment_range.to_dict()
|
||||
|
||||
if range_data['default']:
|
||||
# TODO(kailun): To use
|
||||
# range_exc.NetworkSegmentRangeDefaultReadOnly when the
|
||||
# neutron-lib patch https://review.openstack.org/640777 is
|
||||
# merged and released.
|
||||
message = _("Network Segment Range %s is a "
|
||||
"default segment range which could not be "
|
||||
"updated or deleted.") % id
|
||||
raise lib_exc.BadRequest(resource=range_def.RESOURCE_NAME,
|
||||
msg=message)
|
||||
|
||||
if self._is_network_segment_range_referenced(
|
||||
context, range_data):
|
||||
# TODO(kailun): To use
|
||||
# range_exc.NetworkSegmentRangeReferencedByProject when the
|
||||
# neutron-lib patch https://review.openstack.org/640777 is
|
||||
# merged and released.
|
||||
message = _("Network Segment Range %s is referenced by "
|
||||
"one or more tenant networks.") % id
|
||||
raise lib_exc.InUse(resource=range_def.RESOURCE_NAME,
|
||||
msg=message)
|
||||
network_segment_range.delete()
|
||||
|
||||
self.type_manager.update_network_segment_range_allocations(
|
||||
network_segment_range['network_type'])
|
@ -327,6 +327,13 @@ def get_random_port_binding_statuses():
|
||||
return random.choice(n_const.PORT_BINDING_STATUSES)
|
||||
|
||||
|
||||
def get_random_network_segment_range_network_type():
|
||||
return random.choice([constants.TYPE_VLAN,
|
||||
constants.TYPE_VXLAN,
|
||||
constants.TYPE_GRE,
|
||||
constants.TYPE_GENEVE])
|
||||
|
||||
|
||||
def is_bsd():
|
||||
"""Return True on BSD-based systems."""
|
||||
|
||||
|
@ -120,7 +120,7 @@ class StandardAttrAPIImapctTestCase(testlib_api.SqlTestCase):
|
||||
expected = ['subnets', 'trunks', 'routers', 'segments',
|
||||
'security_group_rules', 'networks', 'policies',
|
||||
'subnetpools', 'ports', 'security_groups', 'floatingips',
|
||||
'logs']
|
||||
'logs', 'network_segment_ranges']
|
||||
self.assertEqual(
|
||||
set(expected),
|
||||
set(standard_attr.get_standard_attr_resource_model_map().keys())
|
||||
@ -132,7 +132,8 @@ class StandardAttrAPIImapctTestCase(testlib_api.SqlTestCase):
|
||||
# should be exposed in release note for API users. And also it should
|
||||
# be list as other tag support resources in doc/source/devref/tag.rst
|
||||
expected = ['subnets', 'trunks', 'routers', 'networks', 'policies',
|
||||
'subnetpools', 'ports', 'security_groups', 'floatingips']
|
||||
'subnetpools', 'ports', 'security_groups', 'floatingips',
|
||||
'network_segment_ranges']
|
||||
self.assertEqual(
|
||||
set(expected),
|
||||
set(standard_attr.get_tag_resource_parent_map().keys())
|
||||
|
350
neutron/tests/unit/extensions/test_network_segment_range.py
Normal file
350
neutron/tests/unit/extensions/test_network_segment_range.py
Normal file
@ -0,0 +1,350 @@
|
||||
# Copyright (c) 2019 Intel Corporation.
|
||||
#
|
||||
# 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 mock
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import context
|
||||
from oslo_config import cfg
|
||||
import webob.exc
|
||||
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import segments_db
|
||||
from neutron.extensions import network_segment_range as ext_range
|
||||
from neutron.services.network_segment_range import plugin as plugin_range
|
||||
from neutron.tests.unit.db import test_db_base_plugin_v2
|
||||
|
||||
SERVICE_PLUGIN_KLASS = ('neutron.services.network_segment_range.plugin.'
|
||||
'NetworkSegmentRangePlugin')
|
||||
TEST_PLUGIN_KLASS = (
|
||||
'neutron.tests.unit.extensions.test_network_segment_range.'
|
||||
'NetworkSegmentRangeTestPlugin')
|
||||
TARGET_PLUGIN = 'neutron.plugins.ml2.plugin.Ml2Plugin'
|
||||
|
||||
|
||||
class NetworkSegmentRangeExtensionManager(object):
|
||||
|
||||
def get_resources(self):
|
||||
return ext_range.Network_segment_range.get_resources()
|
||||
|
||||
def get_actions(self):
|
||||
return []
|
||||
|
||||
def get_request_extensions(self):
|
||||
return []
|
||||
|
||||
|
||||
class NetworkSegmentRangeTestBase(test_db_base_plugin_v2.
|
||||
NeutronDbPluginV2TestCase):
|
||||
|
||||
def _create_network_segment_range(self, fmt, expected_res_status=None,
|
||||
**kwargs):
|
||||
network_segment_range = {'network_segment_range': {}}
|
||||
for k, v in kwargs.items():
|
||||
network_segment_range['network_segment_range'][k] = str(v)
|
||||
|
||||
network_segment_range_req = self.new_create_request(
|
||||
'network-segment-ranges', network_segment_range, fmt)
|
||||
|
||||
network_segment_range_res = network_segment_range_req.get_response(
|
||||
self.ext_api)
|
||||
|
||||
if expected_res_status:
|
||||
self.assertEqual(expected_res_status,
|
||||
network_segment_range_res.status_int)
|
||||
return network_segment_range_res
|
||||
|
||||
def network_segment_range(self, **kwargs):
|
||||
res = self._create_network_segment_range(self.fmt, **kwargs)
|
||||
if res.status_int >= webob.exc.HTTPClientError.code:
|
||||
raise webob.exc.HTTPClientError(code=res.status_int)
|
||||
return self.deserialize(self.fmt, res)
|
||||
|
||||
def _test_create_network_segment_range(self, expected=None, **kwargs):
|
||||
network_segment_range = self.network_segment_range(**kwargs)
|
||||
self._validate_resource(network_segment_range, kwargs,
|
||||
'network_segment_range')
|
||||
if expected:
|
||||
self._compare_resource(network_segment_range, expected,
|
||||
'network_segment_range')
|
||||
return network_segment_range
|
||||
|
||||
def _test_update_network_segment_range(self, range_id,
|
||||
data, expected=None):
|
||||
update_req = self.new_update_request(
|
||||
'network-segment-ranges', data, range_id)
|
||||
|
||||
update_res = update_req.get_response(self.ext_api)
|
||||
if expected:
|
||||
network_segment_range = self.deserialize(self.fmt, update_res)
|
||||
self._compare_resource(network_segment_range, expected,
|
||||
'network_segment_range')
|
||||
return network_segment_range
|
||||
|
||||
return update_res
|
||||
|
||||
|
||||
class NetworkSegmentRangeTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
plugin_range.NetworkSegmentRangePlugin):
|
||||
"""Test plugin to mixin the network segment range extension."""
|
||||
__native_pagination_support = True
|
||||
__native_sorting_support = True
|
||||
__filter_validation_support = True
|
||||
|
||||
supported_extension_aliases = ["provider", "network-segment-range"]
|
||||
|
||||
def __init__(self):
|
||||
super(NetworkSegmentRangeTestPlugin, self).__init__()
|
||||
self.type_manager = mock.Mock()
|
||||
|
||||
|
||||
class TestNetworkSegmentRange(NetworkSegmentRangeTestBase):
|
||||
|
||||
def setUp(self, plugin=None):
|
||||
if not plugin:
|
||||
plugin = TEST_PLUGIN_KLASS
|
||||
service_plugins = {'network_segment_range_plugin_name':
|
||||
SERVICE_PLUGIN_KLASS}
|
||||
cfg.CONF.set_override('service_plugins', [SERVICE_PLUGIN_KLASS])
|
||||
ext_mgr = NetworkSegmentRangeExtensionManager()
|
||||
super(TestNetworkSegmentRange, self).setUp(
|
||||
plugin=plugin, ext_mgr=ext_mgr, service_plugins=service_plugins)
|
||||
|
||||
def _test_create_network_segment_range(self, expected=None, **kwargs):
|
||||
for d in (kwargs, expected):
|
||||
if d is None:
|
||||
continue
|
||||
d.setdefault('name', '')
|
||||
d.setdefault('shared', True)
|
||||
d.setdefault('project_id', None)
|
||||
d.setdefault('network_type', constants.TYPE_VLAN)
|
||||
d.setdefault('physical_network', 'phys_net')
|
||||
d.setdefault('minimum', 200)
|
||||
d.setdefault('maximum', 300)
|
||||
return (super(TestNetworkSegmentRange, self).
|
||||
_test_create_network_segment_range(expected, **kwargs))
|
||||
|
||||
def test_create_network_segment_range_empty_name(self):
|
||||
expected_range = {'name': '',
|
||||
'shared': True,
|
||||
'project_id': None,
|
||||
'network_type': constants.TYPE_VLAN,
|
||||
'physical_network': 'phys_net',
|
||||
'minimum': 200,
|
||||
'maximum': 300}
|
||||
self._test_create_network_segment_range(expected=expected_range)
|
||||
|
||||
def test_create_network_segment_range_with_name(self):
|
||||
expected_range = {'name': 'foo-range-name',
|
||||
'shared': True,
|
||||
'project_id': None,
|
||||
'network_type': constants.TYPE_VLAN,
|
||||
'physical_network': 'phys_net',
|
||||
'minimum': 200,
|
||||
'maximum': 300}
|
||||
self._test_create_network_segment_range(
|
||||
name='foo-range-name',
|
||||
expected=expected_range)
|
||||
|
||||
def test_create_network_segment_range_unsupported_network_type(self):
|
||||
exc = self.assertRaises(webob.exc.HTTPClientError,
|
||||
self._test_create_network_segment_range,
|
||||
network_type='foo-network-type')
|
||||
self.assertEqual(webob.exc.HTTPClientError.code, exc.code)
|
||||
self.assertIn('The server could not comply with the request',
|
||||
exc.explanation)
|
||||
|
||||
def test_create_network_segment_range_no_physical_network(self):
|
||||
expected_range = {'shared': True,
|
||||
'project_id': None,
|
||||
'network_type': constants.TYPE_VXLAN,
|
||||
'physical_network': None}
|
||||
self._test_create_network_segment_range(
|
||||
network_type=constants.TYPE_VXLAN,
|
||||
physical_network=None,
|
||||
expected=expected_range)
|
||||
|
||||
def test_create_network_segment_range_tenant_specific(self):
|
||||
expected_range = {'shared': False,
|
||||
'project_id': test_db_base_plugin_v2.TEST_TENANT_ID,
|
||||
'network_type': constants.TYPE_VLAN,
|
||||
'physical_network': 'phys_net',
|
||||
'minimum': 200,
|
||||
'maximum': 300}
|
||||
self._test_create_network_segment_range(
|
||||
shared=False,
|
||||
project_id=test_db_base_plugin_v2.TEST_TENANT_ID,
|
||||
network_type=constants.TYPE_VLAN,
|
||||
physical_network='phys_net',
|
||||
expected=expected_range)
|
||||
|
||||
def test_create_network_segment_ranges_in_certain_order(self):
|
||||
ctx = context.get_admin_context()
|
||||
range1 = self._test_create_network_segment_range(
|
||||
name='foo-range1', physical_network='phys_net1')
|
||||
range2 = self._test_create_network_segment_range(
|
||||
name='foo-range2', physical_network='phys_net2')
|
||||
range3 = self._test_create_network_segment_range(
|
||||
name='foo-range3', physical_network='phys_net3')
|
||||
network_segment_ranges = (
|
||||
NetworkSegmentRangeTestPlugin.get_network_segment_ranges(
|
||||
NetworkSegmentRangeTestPlugin(), ctx))
|
||||
self.assertEqual(range1['network_segment_range']['id'],
|
||||
network_segment_ranges[0]['id'])
|
||||
self.assertEqual(range2['network_segment_range']['id'],
|
||||
network_segment_ranges[1]['id'])
|
||||
self.assertEqual(range3['network_segment_range']['id'],
|
||||
network_segment_ranges[2]['id'])
|
||||
|
||||
def test_create_network_segment_range_failed_with_vlan_minimum_id(self):
|
||||
exc = self.assertRaises(webob.exc.HTTPClientError,
|
||||
self._test_create_network_segment_range,
|
||||
minimum=0)
|
||||
self.assertEqual(webob.exc.HTTPClientError.code, exc.code)
|
||||
self.assertIn('The server could not comply with the request',
|
||||
exc.explanation)
|
||||
|
||||
def test_create_network_segment_range_failed_with_vlan_maximum_id(self):
|
||||
exc = self.assertRaises(webob.exc.HTTPClientError,
|
||||
self._test_create_network_segment_range,
|
||||
minimum=4095)
|
||||
self.assertEqual(webob.exc.HTTPServerError.code, exc.code)
|
||||
self.assertIn('The server could not comply with the request',
|
||||
exc.explanation)
|
||||
|
||||
def test_create_network_segment_range_failed_with_tunnel_minimum_id(self):
|
||||
tunnel_type = [constants.TYPE_VXLAN,
|
||||
constants.TYPE_GRE,
|
||||
constants.TYPE_GENEVE]
|
||||
for network_type in tunnel_type:
|
||||
exc = self.assertRaises(webob.exc.HTTPClientError,
|
||||
self._test_create_network_segment_range,
|
||||
network_type=network_type,
|
||||
physical_network=None,
|
||||
minimum=0)
|
||||
self.assertEqual(webob.exc.HTTPClientError.code, exc.code)
|
||||
self.assertIn('The server could not comply with the request',
|
||||
exc.explanation)
|
||||
|
||||
def test_create_network_segment_range_failed_with_tunnel_maximum_id(self):
|
||||
expected_res = [(constants.TYPE_VXLAN, 2 ** 24),
|
||||
(constants.TYPE_GRE, 2 ** 32),
|
||||
(constants.TYPE_GENEVE, 2 ** 24)]
|
||||
for network_type, max_id in expected_res:
|
||||
exc = self.assertRaises(webob.exc.HTTPClientError,
|
||||
self._test_create_network_segment_range,
|
||||
network_type=network_type,
|
||||
physical_network=None,
|
||||
maximum=max_id)
|
||||
if network_type == constants.TYPE_GRE:
|
||||
self.assertEqual(webob.exc.HTTPClientError.code, exc.code)
|
||||
else:
|
||||
self.assertEqual(webob.exc.HTTPServerError.code, exc.code)
|
||||
self.assertIn('The server could not comply with the request',
|
||||
exc.explanation)
|
||||
|
||||
def test_update_network_segment_range_set_name(self):
|
||||
network_segment_range = self._test_create_network_segment_range()
|
||||
with mock.patch.object(segments_db, 'min_max_actual_segments_in_range',
|
||||
return_value=(None, None)):
|
||||
result = self._update(
|
||||
'network-segment-ranges',
|
||||
network_segment_range['network_segment_range']['id'],
|
||||
{'network_segment_range': {'name': 'foo-name'}},
|
||||
expected_code=webob.exc.HTTPOk.code)
|
||||
self.assertEqual('foo-name',
|
||||
result['network_segment_range']['name'])
|
||||
|
||||
def test_update_network_segment_range_set_name_to_empty(self):
|
||||
network_segment_range = self._test_create_network_segment_range(
|
||||
name='foo-range-name')
|
||||
with mock.patch.object(segments_db, 'min_max_actual_segments_in_range',
|
||||
return_value=(None, None)):
|
||||
result = self._update(
|
||||
'network-segment-ranges',
|
||||
network_segment_range['network_segment_range']['id'],
|
||||
{'network_segment_range': {'name': ''}},
|
||||
expected_code=webob.exc.HTTPOk.code)
|
||||
self.assertEqual('', result['network_segment_range']['name'])
|
||||
|
||||
def test_update_network_segment_range_min_max(self):
|
||||
network_segment_range = self._test_create_network_segment_range()
|
||||
with mock.patch.object(segments_db, 'min_max_actual_segments_in_range',
|
||||
return_value=(None, None)):
|
||||
result = self._update(
|
||||
'network-segment-ranges',
|
||||
network_segment_range['network_segment_range']['id'],
|
||||
{'network_segment_range': {'minimum': 1200, 'maximum': 1300}},
|
||||
expected_code=webob.exc.HTTPOk.code)
|
||||
self.assertEqual(1200, result['network_segment_range']['minimum'])
|
||||
self.assertEqual(1300, result['network_segment_range']['maximum'])
|
||||
|
||||
def test_get_network_segment_range(self):
|
||||
network_segment_range = self._test_create_network_segment_range()
|
||||
req = self.new_show_request(
|
||||
'network-segment-ranges',
|
||||
network_segment_range['network_segment_range']['id'])
|
||||
res = self.deserialize(self.fmt, req.get_response(self.ext_api))
|
||||
self.assertEqual(
|
||||
network_segment_range['network_segment_range']['id'],
|
||||
res['network_segment_range']['id'])
|
||||
|
||||
def test_list_network_segment_ranges(self):
|
||||
self._test_create_network_segment_range(name='foo-range1')
|
||||
self._test_create_network_segment_range(
|
||||
name='foo-range2', minimum=400, maximum=500)
|
||||
res = self._list('network-segment-ranges')
|
||||
self.assertEqual(2, len(res['network_segment_ranges']))
|
||||
|
||||
def test_list_network_segment_ranges_with_sort(self):
|
||||
range1 = self._test_create_network_segment_range(
|
||||
name='foo-range1', physical_network='phys_net1')
|
||||
range2 = self._test_create_network_segment_range(
|
||||
name='foo-range2', physical_network='phys_net2')
|
||||
self._test_list_with_sort('network-segment-range',
|
||||
(range2, range1),
|
||||
[('physical_network', 'desc')])
|
||||
|
||||
def test_list_network_segment_ranges_with_pagination(self):
|
||||
range1 = self._test_create_network_segment_range(
|
||||
name='foo-range1', physical_network='phys_net1')
|
||||
range2 = self._test_create_network_segment_range(
|
||||
name='foo-range2', physical_network='phys_net2')
|
||||
range3 = self._test_create_network_segment_range(
|
||||
name='foo-range3', physical_network='phys_net3')
|
||||
self._test_list_with_pagination(
|
||||
'network-segment-range',
|
||||
(range1, range2, range3),
|
||||
('physical_network', 'asc'), 2, 2)
|
||||
|
||||
def test_list_network_segment_ranges_with_pagination_reverse(self):
|
||||
range1 = self._test_create_network_segment_range(
|
||||
name='foo-range1', physical_network='phys_net1')
|
||||
range2 = self._test_create_network_segment_range(
|
||||
name='foo-range2', physical_network='phys_net2')
|
||||
range3 = self._test_create_network_segment_range(
|
||||
name='foo-range3', physical_network='phys_net3')
|
||||
self._test_list_with_pagination_reverse(
|
||||
'network-segment-range',
|
||||
(range1, range2, range3),
|
||||
('physical_network', 'asc'), 2, 2)
|
||||
|
||||
def test_delete_network_segment_range(self):
|
||||
network_segment_range = self._test_create_network_segment_range()
|
||||
with mock.patch.object(segments_db, 'network_segments_exist_in_range',
|
||||
return_value=False):
|
||||
self._delete('network-segment-ranges',
|
||||
network_segment_range['network_segment_range']['id'])
|
||||
self._show('network-segment-ranges',
|
||||
network_segment_range['network_segment_range']['id'],
|
||||
expected_code=webob.exc.HTTPNotFound.code)
|
@ -517,6 +517,8 @@ FIELD_TYPE_VALUE_GENERATOR_MAP = {
|
||||
common_types.IpProtocolEnumField: tools.get_random_ip_protocol,
|
||||
common_types.ListOfIPNetworksField: get_list_of_random_networks,
|
||||
common_types.MACAddressField: tools.get_random_EUI,
|
||||
common_types.NetworkSegmentRangeNetworkTypeEnumField:
|
||||
tools.get_random_network_segment_range_network_type,
|
||||
common_types.PortBindingStatusEnumField:
|
||||
tools.get_random_port_binding_statuses,
|
||||
common_types.PortRangeField: tools.get_random_port,
|
||||
@ -595,7 +597,7 @@ class _BaseObjectTestCase(object):
|
||||
self.valid_field = [f for f in self._test_class.fields
|
||||
if f not in invalid_fields][0]
|
||||
self.valid_field_filter = {self.valid_field:
|
||||
self.obj_fields[-1][self.valid_field]}
|
||||
self.obj_fields[-1].get(self.valid_field)}
|
||||
self.obj_registry = self.useFixture(
|
||||
NeutronObjectRegistryFixture())
|
||||
self.obj_registry.register(FakeSmallNeutronObject)
|
||||
|
@ -283,3 +283,22 @@ class DictOfMiscValuesFieldTest(test_base.BaseTestCase, TestField):
|
||||
for in_val, out_val in self.coerce_good_values:
|
||||
self.assertEqual(jsonutils.dumps(in_val),
|
||||
self.field.stringify(in_val))
|
||||
|
||||
|
||||
class NetworkSegmentRangeNetworkTypeEnumFieldTest(test_base.BaseTestCase,
|
||||
TestField):
|
||||
def setUp(self):
|
||||
super(NetworkSegmentRangeNetworkTypeEnumFieldTest, self).setUp()
|
||||
self.field = common_types.NetworkSegmentRangeNetworkTypeEnumField()
|
||||
self.coerce_good_values = [(val, val)
|
||||
for val in [const.TYPE_VLAN,
|
||||
const.TYPE_VXLAN,
|
||||
const.TYPE_GRE,
|
||||
const.TYPE_GENEVE]]
|
||||
self.coerce_bad_values = [const.TYPE_FLAT, 'foo-network-type']
|
||||
self.to_primitive_values = self.coerce_good_values
|
||||
self.from_primitive_values = self.coerce_good_values
|
||||
|
||||
def test_stringify(self):
|
||||
for in_val, out_val in self.coerce_good_values:
|
||||
self.assertEqual("'%s'" % in_val, self.field.stringify(in_val))
|
||||
|
138
neutron/tests/unit/objects/test_network_segment_range.py
Normal file
138
neutron/tests/unit/objects/test_network_segment_range.py
Normal file
@ -0,0 +1,138 @@
|
||||
# Copyright (c) 2019 Intel Corporation.
|
||||
#
|
||||
# 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
|
||||
|
||||
import mock
|
||||
from neutron_lib import constants
|
||||
from neutron_lib.utils import helpers
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.objects import network as net_obj
|
||||
from neutron.objects import network_segment_range
|
||||
from neutron.objects.plugins.ml2 import vlanallocation as vlan_alloc_obj
|
||||
from neutron.tests.unit.objects import test_base as obj_test_base
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
TEST_TENANT_ID = '46f70361-ba71-4bd0-9769-3573fd227c4b'
|
||||
TEST_PHYSICAL_NETWORK = 'phys_net'
|
||||
|
||||
|
||||
class NetworkSegmentRangeIfaceObjectTestCase(
|
||||
obj_test_base.BaseObjectIfaceTestCase):
|
||||
|
||||
_test_class = network_segment_range.NetworkSegmentRange
|
||||
|
||||
def setUp(self):
|
||||
self._mock_get_available_allocation = mock.patch.object(
|
||||
network_segment_range.NetworkSegmentRange,
|
||||
'_get_available_allocation',
|
||||
return_value=[])
|
||||
self.mock_get_available_allocation = (
|
||||
self._mock_get_available_allocation.start())
|
||||
self._mock_get_used_allocation_mapping = mock.patch.object(
|
||||
network_segment_range.NetworkSegmentRange,
|
||||
'_get_used_allocation_mapping',
|
||||
return_value={})
|
||||
self.mock_get_used_allocation_mapping = (
|
||||
self._mock_get_used_allocation_mapping.start())
|
||||
super(NetworkSegmentRangeIfaceObjectTestCase, self).setUp()
|
||||
# `project_id` and `physical_network` attributes in
|
||||
# network_segment_range are nullable, depending on the value of
|
||||
# `shared` and `network_type` respectively.
|
||||
# Hack to always populate test project_id and physical_network
|
||||
# fields in network segment range Iface object testing so that related
|
||||
# tests like `test_extra_fields`, `test_create_updates_from_db_object`,
|
||||
# `test_update_updates_from_db_object` can have those fields.
|
||||
# Alternatives can be skipping those tests when executing
|
||||
# NetworkSegmentRangeIfaceObjectTestCase, or making base test case
|
||||
# adjustments.
|
||||
self.update_obj_fields({'project_id': TEST_TENANT_ID,
|
||||
'physical_network': TEST_PHYSICAL_NETWORK})
|
||||
|
||||
|
||||
class NetworkSegmentRangeDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
||||
testlib_api.SqlTestCase):
|
||||
|
||||
_test_class = network_segment_range.NetworkSegmentRange
|
||||
|
||||
def _create_test_vlan_allocation(self, vlan_id=None, allocated=False):
|
||||
attr = self.get_random_object_fields(vlan_alloc_obj.VlanAllocation)
|
||||
attr.update({
|
||||
'vlan_id': vlan_id or random.randint(
|
||||
constants.MIN_VLAN_TAG, constants.MAX_VLAN_TAG),
|
||||
'physical_network': 'foo',
|
||||
'allocated': allocated})
|
||||
_vlan_allocation = vlan_alloc_obj.VlanAllocation(self.context, **attr)
|
||||
_vlan_allocation.create()
|
||||
return _vlan_allocation
|
||||
|
||||
def _create_test_network(self, name=None, network_id=None):
|
||||
name = "test-network-%s" % helpers.get_random_string(4)
|
||||
network_id = (uuidutils.generate_uuid() if network_id is None
|
||||
else network_id)
|
||||
_network = net_obj.Network(self.context, name=name, id=network_id,
|
||||
project_id=uuidutils.generate_uuid())
|
||||
_network.create()
|
||||
return _network
|
||||
|
||||
def _create_test_vlan_segment(self, segmentation_id=None, network_id=None):
|
||||
attr = self.get_random_object_fields(net_obj.NetworkSegment)
|
||||
attr.update({
|
||||
'network_id': network_id or self._create_test_network_id(),
|
||||
'network_type': constants.TYPE_VLAN,
|
||||
'physical_network': 'foo',
|
||||
'segmentation_id': segmentation_id or random.randint(
|
||||
constants.MIN_VLAN_TAG, constants.MAX_VLAN_TAG)})
|
||||
_segment = net_obj.NetworkSegment(self.context, **attr)
|
||||
_segment.create()
|
||||
return _segment
|
||||
|
||||
def _create_test_vlan_network_segment_range_obj(self, minimum, maximum):
|
||||
kwargs = self.get_random_db_fields()
|
||||
kwargs.update({'network_type': constants.TYPE_VLAN,
|
||||
'physical_network': 'foo',
|
||||
'minimum': minimum,
|
||||
'maximum': maximum})
|
||||
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)
|
||||
return obj
|
||||
|
||||
def test__get_available_allocation(self):
|
||||
range_minimum = 100
|
||||
range_maximum = 120
|
||||
to_alloc = range(range_minimum, range_maximum - 5)
|
||||
not_to_alloc = range(range_maximum - 5, range_maximum + 1)
|
||||
for vlan_id in to_alloc:
|
||||
self._create_test_vlan_allocation(vlan_id=vlan_id, allocated=True)
|
||||
for vlan_id in not_to_alloc:
|
||||
self._create_test_vlan_allocation(vlan_id=vlan_id, allocated=False)
|
||||
obj = self._create_test_vlan_network_segment_range_obj(range_minimum,
|
||||
range_maximum)
|
||||
available_alloc = self._test_class._get_available_allocation(obj)
|
||||
self.assertItemsEqual(not_to_alloc, available_alloc)
|
||||
|
||||
def test__get_used_allocation_mapping(self):
|
||||
alloc_mapping = {}
|
||||
for _ in range(5):
|
||||
network = self._create_test_network()
|
||||
segment = self._create_test_vlan_segment(network_id=network.id)
|
||||
alloc_mapping.update({segment.segmentation_id: network.project_id})
|
||||
|
||||
obj = self._create_test_vlan_network_segment_range_obj(
|
||||
minimum=min(list(alloc_mapping.keys())),
|
||||
maximum=max(list(alloc_mapping.keys())))
|
||||
ret_alloc_mapping = self._test_class._get_used_allocation_mapping(obj)
|
||||
self.assertDictEqual(alloc_mapping, ret_alloc_mapping)
|
@ -62,6 +62,7 @@ object_data = {
|
||||
'NetworkPortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
|
||||
'NetworkRBAC': '1.2-192845c5ed0718e1c54fac36936fcd7d',
|
||||
'NetworkSegment': '1.0-57b7f2960971e3b95ded20cbc59244a8',
|
||||
'NetworkSegmentRange': '1.0-bdec1fffc9058ea676089b1f2f2b3cf3',
|
||||
'Port': '1.4-1b6183bccfc2cd210919a1a72faefce1',
|
||||
'PortBinding': '1.0-3306deeaa6deb01e33af06777d48d578',
|
||||
'PortBindingLevel': '1.1-50d47f63218f87581b6cd9a62db574e5',
|
||||
|
240
neutron/tests/unit/services/network_segment_range/test_plugin.py
Normal file
240
neutron/tests/unit/services/network_segment_range/test_plugin.py
Normal file
@ -0,0 +1,240 @@
|
||||
# Copyright (c) 2019 Intel Corporation.
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import context
|
||||
from neutron_lib import exceptions as exc
|
||||
from neutron_lib.utils import helpers
|
||||
from oslo_config import cfg
|
||||
|
||||
from neutron.db import segments_db
|
||||
from neutron.services.network_segment_range import plugin as range_plugin
|
||||
from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
SERVICE_PLUGIN_KLASS = ('neutron.services.network_segment_range.plugin.'
|
||||
'NetworkSegmentRangePlugin')
|
||||
|
||||
|
||||
class TestNetworkSegmentRange(testlib_api.SqlTestCase):
|
||||
|
||||
_foo_range = {'name': 'foo-range',
|
||||
'default': False,
|
||||
'shared': False,
|
||||
'project_id': test_plugin.TEST_TENANT_ID,
|
||||
'network_type': 'foo_network_type',
|
||||
'physical_network': 'foo_phys_net',
|
||||
'minimum': 200,
|
||||
'maximum': 300}
|
||||
|
||||
_flat_range = {'name': 'foo-flat-range',
|
||||
'default': False,
|
||||
'shared': False,
|
||||
'project_id': test_plugin.TEST_TENANT_ID,
|
||||
'network_type': constants.TYPE_FLAT,
|
||||
'physical_network': None,
|
||||
'minimum': 0,
|
||||
'maximum': 0}
|
||||
|
||||
_vlan_range = {'name': 'foo-vlan-range',
|
||||
'default': False,
|
||||
'shared': False,
|
||||
'project_id': test_plugin.TEST_TENANT_ID,
|
||||
'network_type': constants.TYPE_VLAN,
|
||||
'physical_network': 'phys_net',
|
||||
'minimum': 200,
|
||||
'maximum': 300}
|
||||
|
||||
_vxlan_range = {'name': 'foo-vxlan-range',
|
||||
'default': False,
|
||||
'shared': False,
|
||||
'project_id': test_plugin.TEST_TENANT_ID,
|
||||
'network_type': constants.TYPE_VXLAN,
|
||||
'physical_network': None,
|
||||
'minimum': 400,
|
||||
'maximum': 500}
|
||||
|
||||
_gre_range = {'name': 'foo-vlan-range',
|
||||
'default': False,
|
||||
'shared': False,
|
||||
'project_id': test_plugin.TEST_TENANT_ID,
|
||||
'network_type': constants.TYPE_GRE,
|
||||
'physical_network': None,
|
||||
'minimum': 600,
|
||||
'maximum': 700}
|
||||
|
||||
_geneve_range = {'name': 'foo-geneve-range',
|
||||
'default': False,
|
||||
'shared': False,
|
||||
'project_id': test_plugin.TEST_TENANT_ID,
|
||||
'network_type': constants.TYPE_GENEVE,
|
||||
'physical_network': None,
|
||||
'minimum': 800,
|
||||
'maximum': 900}
|
||||
|
||||
def setUp(self):
|
||||
super(TestNetworkSegmentRange, self).setUp()
|
||||
with mock.patch("neutron_lib.plugins.directory.get_plugin"):
|
||||
self.plugin = range_plugin.NetworkSegmentRangePlugin()
|
||||
self.context = context.get_admin_context()
|
||||
cfg.CONF.set_override('service_plugins', [SERVICE_PLUGIN_KLASS])
|
||||
|
||||
def _validate_resource(self, resource, keys, res_name):
|
||||
for k in keys:
|
||||
self.assertIn(k, resource[res_name])
|
||||
if isinstance(keys[k], list):
|
||||
self.assertEqual(
|
||||
sorted(keys[k], key=helpers.safe_sort_key),
|
||||
sorted(resource[res_name][k], key=helpers.safe_sort_key))
|
||||
else:
|
||||
self.assertEqual(keys[k], resource[res_name][k])
|
||||
|
||||
def test__is_network_segment_range_referenced(self):
|
||||
with mock.patch.object(segments_db,
|
||||
'network_segments_exist_in_range',
|
||||
return_value=True):
|
||||
self.assertTrue(self.plugin._is_network_segment_range_referenced(
|
||||
self.context, self._vlan_range))
|
||||
|
||||
def test__is_network_segment_range_unreferenced(self):
|
||||
with mock.patch.object(segments_db,
|
||||
'network_segments_exist_in_range',
|
||||
return_value=False):
|
||||
self.assertFalse(self.plugin._is_network_segment_range_referenced(
|
||||
self.context, self._vlan_range))
|
||||
|
||||
def test__is_network_segment_range_type_supported(self):
|
||||
for foo_range in [self._vlan_range, self._vxlan_range,
|
||||
self._gre_range, self._geneve_range]:
|
||||
self.assertTrue(
|
||||
self.plugin.
|
||||
_is_network_segment_range_type_supported(
|
||||
foo_range['network_type']))
|
||||
|
||||
def test__is_network_segment_range_type_unsupported(self):
|
||||
self.assertRaises(
|
||||
exc.NeutronException,
|
||||
self.plugin._is_network_segment_range_type_supported,
|
||||
self._foo_range['network_type'])
|
||||
self.assertRaises(
|
||||
exc.NeutronException,
|
||||
self.plugin._is_network_segment_range_type_supported,
|
||||
self._flat_range['network_type'])
|
||||
|
||||
def test__are_allocated_segments_in_range_impacted(self):
|
||||
existing_range = self._foo_range
|
||||
updated_range = self._vlan_range
|
||||
impacted_existing_ranges = [(150, 250), (250, 320),
|
||||
(200, 300), (180, 330)]
|
||||
for ret in impacted_existing_ranges:
|
||||
with mock.patch.object(segments_db,
|
||||
'min_max_actual_segments_in_range',
|
||||
return_value=ret):
|
||||
self.assertTrue(
|
||||
self.plugin._are_allocated_segments_in_range_impacted(
|
||||
self.context, existing_range, updated_range))
|
||||
|
||||
def test__are_allocated_segments_in_range_unimpacted(self):
|
||||
existing_range = self._foo_range
|
||||
updated_range = self._vlan_range
|
||||
with mock.patch.object(segments_db,
|
||||
'min_max_actual_segments_in_range',
|
||||
return_value=(220, 270)):
|
||||
self.assertFalse(
|
||||
self.plugin._are_allocated_segments_in_range_impacted(
|
||||
self.context, existing_range, updated_range))
|
||||
|
||||
def test_create_network_segment_range(self):
|
||||
test_range = self._vlan_range
|
||||
network_segment_range = {'network_segment_range': test_range}
|
||||
ret = self.plugin.create_network_segment_range(self.context,
|
||||
network_segment_range)
|
||||
res = {'network_segment_range': ret}
|
||||
self._validate_resource(res, test_range, 'network_segment_range')
|
||||
|
||||
def test_create_network_segment_range_failed_with_unsupported_network_type(
|
||||
self):
|
||||
test_range = self._flat_range
|
||||
network_segment_range = {'network_segment_range': test_range}
|
||||
self.assertRaises(
|
||||
exc.NeutronException,
|
||||
self.plugin.create_network_segment_range,
|
||||
self.context,
|
||||
network_segment_range)
|
||||
|
||||
def test_update_network_segment_range(self):
|
||||
test_range = self._vlan_range
|
||||
network_segment_range = {'network_segment_range': test_range}
|
||||
ret = self.plugin.create_network_segment_range(self.context,
|
||||
network_segment_range)
|
||||
updated_network_segment_range = {
|
||||
'network_segment_range': {'minimum': 700, 'maximum': 800}}
|
||||
with mock.patch.object(self.plugin,
|
||||
'_are_allocated_segments_in_range_impacted',
|
||||
return_value=False):
|
||||
updated_ret = self.plugin.update_network_segment_range(
|
||||
self.context, ret['id'], updated_network_segment_range)
|
||||
res = {'network_segment_range': updated_ret}
|
||||
test_range['minimum'] = 700
|
||||
test_range['maximum'] = 800
|
||||
self._validate_resource(res, test_range, 'network_segment_range')
|
||||
|
||||
def test_update_network_segment_range_failed_with_impacted_existing_range(
|
||||
self):
|
||||
test_range = self._vlan_range
|
||||
network_segment_range = {'network_segment_range': test_range}
|
||||
ret = self.plugin.create_network_segment_range(self.context,
|
||||
network_segment_range)
|
||||
updated_network_segment_range = {
|
||||
'network_segment_range': {'minimum': 150, 'maximum': 250}}
|
||||
with mock.patch.object(self.plugin,
|
||||
'_are_allocated_segments_in_range_impacted',
|
||||
return_value=True):
|
||||
self.assertRaises(
|
||||
exc.NeutronException,
|
||||
self.plugin.update_network_segment_range,
|
||||
self.context,
|
||||
ret['id'],
|
||||
updated_network_segment_range)
|
||||
|
||||
def test_delete_network_segment_range(self):
|
||||
test_range = self._vlan_range
|
||||
network_segment_range = {'network_segment_range': test_range}
|
||||
ret = self.plugin.create_network_segment_range(self.context,
|
||||
network_segment_range)
|
||||
with mock.patch.object(self.plugin,
|
||||
'_is_network_segment_range_referenced',
|
||||
return_value=False):
|
||||
try:
|
||||
self.plugin.delete_network_segment_range(
|
||||
self.context, ret['id'])
|
||||
except exc.NeutronException:
|
||||
self.fail("delete_network_segment_range raised "
|
||||
"NeutronException unexpectedly!")
|
||||
|
||||
def test_delete_network_segment_range_failed_with_segment_referenced(self):
|
||||
test_range = self._vlan_range
|
||||
network_segment_range = {'network_segment_range': test_range}
|
||||
ret = self.plugin.create_network_segment_range(self.context,
|
||||
network_segment_range)
|
||||
with mock.patch.object(self.plugin,
|
||||
'_is_network_segment_range_referenced',
|
||||
return_value=True):
|
||||
self.assertRaises(
|
||||
exc.NeutronException,
|
||||
self.plugin.delete_network_segment_range,
|
||||
self.context,
|
||||
ret['id'])
|
@ -0,0 +1,25 @@
|
||||
---
|
||||
prelude: >
|
||||
Added support for network segment range management. This introduces the
|
||||
ability for administrators to control the segment ranges globally or on
|
||||
a per-tenant basis via the Neutron API.
|
||||
features:
|
||||
- |
|
||||
Before Stein, network segment ranges were configured as an entry in ML2
|
||||
config file ``/etc/neutron/plugins/ml2/ml2_conf.ini`` that was statically
|
||||
defined for tenant network allocation and therefore had to be managed as
|
||||
part of the host deployment and management.
|
||||
The new ``network-segment-range`` API extension has been introduced, which
|
||||
exposes the network segment ranges to be administered via API. This
|
||||
allows users with admin privileges to be able to dynamically manage
|
||||
the shared and/or tenant specific network segment ranges.
|
||||
Standard attributes with tagging support are introduced to the new
|
||||
resource.
|
||||
The feature is controlled by the newly-added service plugin
|
||||
``network_segment_range``.
|
||||
A set of ``default`` network segment ranges will be created out of
|
||||
the ranges that are defined in the host ML2 config file
|
||||
``/etc/neutron/plugins/ml2/ml2_conf.ini``, such as
|
||||
``network_vlan_ranges``, ``vni_ranges`` for ml2_type_vxlan,
|
||||
``tunnel_id_ranges`` for ml2_type_gre and ``vni_ranges`` for
|
||||
ml2_type_geneve.
|
@ -68,6 +68,7 @@ neutron.service_plugins =
|
||||
auto_allocate = neutron.services.auto_allocate.plugin:Plugin
|
||||
segments = neutron.services.segments.plugin:Plugin
|
||||
network_ip_availability = neutron.services.network_ip_availability.plugin:NetworkIPAvailabilityPlugin
|
||||
network_segment_range = neutron.services.network_segment_range.plugin:NetworkSegmentRangePlugin
|
||||
revisions = neutron.services.revisions.revision_plugin:RevisionPlugin
|
||||
timestamp = neutron.services.timestamp.timestamp_plugin:TimeStampPlugin
|
||||
trunk = neutron.services.trunk.plugin:TrunkPlugin
|
||||
@ -196,6 +197,7 @@ neutron.objects =
|
||||
NetworkPortSecurity = neutron.objects.network:NetworkPortSecurity
|
||||
NetworkRBAC = neutron.objects.network:NetworkRBAC
|
||||
NetworkSegment = neutron.objects.network:NetworkSegment
|
||||
NetworkSegmentRange = neutron.objects.network:NetworkSegmentRange
|
||||
Port = neutron.objects.ports:Port
|
||||
PortBinding = neutron.objects.ports:PortBinding
|
||||
PortBindingLevel = neutron.objects.ports:PortBindingLevel
|
||||
|
Loading…
Reference in New Issue
Block a user