Add partial specs support in ML2 for vlan provider networks
ML2 provider networks partial specs let admins choose some provider network attributes and let neutron choose remaining attributes. This change provides the implementation for VLAN provider networks. In practice, for VLAN provider networks provider:physical_network and provider:segmentation_id choices can be delegated to neutron, in such case neutron will try to find a network in tenant network pools which respects provided provider attributes. DocImpact Related to blueprint provider-network-partial-specs Partial-Bug: #1330562 Change-Id: I2c52c71167edaa153b2e04681273e2f1be8d03aa
This commit is contained in:
parent
6e877945c9
commit
b3202c3283
@ -179,6 +179,11 @@ class NoNetworkAvailable(ResourceExhausted):
|
|||||||
"No tenant network is available for allocation.")
|
"No tenant network is available for allocation.")
|
||||||
|
|
||||||
|
|
||||||
|
class NoNetworkFoundInMaximumAllowedAttempts(ServiceUnavailable):
|
||||||
|
message = _("Unable to create the network. "
|
||||||
|
"No available network found in maximum allowed attempts.")
|
||||||
|
|
||||||
|
|
||||||
class SubnetMismatchForPort(BadRequest):
|
class SubnetMismatchForPort(BadRequest):
|
||||||
message = _("Subnet on port %(port_id)s does not match "
|
message = _("Subnet on port %(port_id)s does not match "
|
||||||
"the requested subnet %(subnet_id)s")
|
"the requested subnet %(subnet_id)s")
|
||||||
|
@ -88,7 +88,8 @@ class TypeDriver(object):
|
|||||||
"""Reserve resource associated with a provider network segment.
|
"""Reserve resource associated with a provider network segment.
|
||||||
|
|
||||||
:param session: database session
|
:param session: database session
|
||||||
:param segment: segment dictionary using keys defined above
|
:param segment: segment dictionary
|
||||||
|
:returns: segment dictionary
|
||||||
|
|
||||||
Called inside transaction context on session to reserve the
|
Called inside transaction context on session to reserve the
|
||||||
type-specific resource for a provider network segment. The
|
type-specific resource for a provider network segment. The
|
||||||
|
140
neutron/plugins/ml2/drivers/helpers.py
Normal file
140
neutron/plugins/ml2/drivers/helpers.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
# Copyright (c) 2014 Thales Services SAS
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo.db import exception as db_exc
|
||||||
|
|
||||||
|
from neutron.common import exceptions as exc
|
||||||
|
from neutron.openstack.common import log
|
||||||
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
|
|
||||||
|
|
||||||
|
# Number of retries to find a valid segment candidate and allocate it
|
||||||
|
DB_MAX_RETRIES = 10
|
||||||
|
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TypeDriverHelper(api.TypeDriver):
|
||||||
|
"""TypeDriver Helper for segment allocation.
|
||||||
|
|
||||||
|
Provide methods helping to perform segment allocation fully or partially
|
||||||
|
specified.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, model):
|
||||||
|
self.model = model
|
||||||
|
self.primary_keys = set(dict(model.__table__.columns))
|
||||||
|
self.primary_keys.remove("allocated")
|
||||||
|
|
||||||
|
def allocate_fully_specified_segment(self, session, **raw_segment):
|
||||||
|
"""Allocate segment fully specified by raw_segment.
|
||||||
|
|
||||||
|
If segment exists, then try to allocate it and return db object
|
||||||
|
If segment does not exists, then try to create it and return db object
|
||||||
|
If allocation/creation failed, then return None
|
||||||
|
"""
|
||||||
|
|
||||||
|
network_type = self.get_type()
|
||||||
|
try:
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
alloc = (session.query(self.model).filter_by(**raw_segment).
|
||||||
|
first())
|
||||||
|
if alloc:
|
||||||
|
if alloc.allocated:
|
||||||
|
# Segment already allocated
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# Segment not allocated
|
||||||
|
LOG.debug("%(type)s segment %(segment)s allocate "
|
||||||
|
"started ",
|
||||||
|
type=network_type, segment=raw_segment)
|
||||||
|
count = (session.query(self.model).
|
||||||
|
filter_by(allocated=False, **raw_segment).
|
||||||
|
update({"allocated": True}))
|
||||||
|
if count:
|
||||||
|
LOG.debug("%(type)s segment %(segment)s allocate "
|
||||||
|
"done ",
|
||||||
|
type=network_type, segment=raw_segment)
|
||||||
|
return alloc
|
||||||
|
|
||||||
|
# Segment allocated or deleted since select
|
||||||
|
LOG.debug("%(type)s segment %(segment)s allocate "
|
||||||
|
"failed: segment has been allocated or "
|
||||||
|
"deleted",
|
||||||
|
type=network_type, segment=raw_segment)
|
||||||
|
|
||||||
|
# Segment to create or already allocated
|
||||||
|
LOG.debug("%(type)s segment %(segment)s create started",
|
||||||
|
type=network_type, segment=raw_segment)
|
||||||
|
alloc = self.model(allocated=True, **raw_segment)
|
||||||
|
alloc.save(session)
|
||||||
|
LOG.debug("%(type)s segment %(segment)s create done",
|
||||||
|
type=network_type, segment=raw_segment)
|
||||||
|
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
# Segment already allocated (insert failure)
|
||||||
|
alloc = None
|
||||||
|
LOG.debug("%(type)s segment %(segment)s create failed",
|
||||||
|
type=network_type, segment=raw_segment)
|
||||||
|
|
||||||
|
return alloc
|
||||||
|
|
||||||
|
def allocate_partially_specified_segment(self, session, **filters):
|
||||||
|
"""Allocate model segment from pool partially specified by filters.
|
||||||
|
|
||||||
|
Return allocated db object or None.
|
||||||
|
"""
|
||||||
|
|
||||||
|
network_type = self.get_type()
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
select = (session.query(self.model).
|
||||||
|
filter_by(allocated=False, **filters))
|
||||||
|
|
||||||
|
# Selected segment can be allocated before update by someone else,
|
||||||
|
# We retry until update success or DB_MAX_RETRIES retries
|
||||||
|
for attempt in range(1, DB_MAX_RETRIES + 1):
|
||||||
|
alloc = select.first()
|
||||||
|
|
||||||
|
if not alloc:
|
||||||
|
# No resource available
|
||||||
|
return
|
||||||
|
|
||||||
|
raw_segment = dict((k, alloc[k]) for k in self.primary_keys)
|
||||||
|
LOG.debug("%(type)s segment allocate from pool, attempt "
|
||||||
|
"%(attempt)s started with %(segment)s ",
|
||||||
|
type=network_type, attempt=attempt,
|
||||||
|
segment=raw_segment)
|
||||||
|
count = (session.query(self.model).
|
||||||
|
filter_by(allocated=False, **raw_segment).
|
||||||
|
update({"allocated": True}))
|
||||||
|
if count:
|
||||||
|
LOG.debug("%(type)s segment allocate from pool, attempt "
|
||||||
|
"%(attempt)s success with %(segment)s ",
|
||||||
|
type=network_type, attempt=attempt,
|
||||||
|
segment=raw_segment)
|
||||||
|
return alloc
|
||||||
|
|
||||||
|
# Segment allocated since select
|
||||||
|
LOG.debug("Allocate %(type)s segment from pool, "
|
||||||
|
"attempt %(attempt)s failed with segment "
|
||||||
|
"%(segment)s",
|
||||||
|
type=network_type, attempt=attempt,
|
||||||
|
segment=raw_segment)
|
||||||
|
|
||||||
|
LOG.warning(_("Allocate %(type)s segment from pool failed "
|
||||||
|
"after %(number)s failed attempts"),
|
||||||
|
{"type": network_type, "number": DB_MAX_RETRIES})
|
||||||
|
raise exc.NoNetworkFoundInMaximumAllowedAttempts
|
@ -14,6 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
from oslo.db import exception as db_exc
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from neutron.common import exceptions as exc
|
from neutron.common import exceptions as exc
|
||||||
@ -100,17 +101,14 @@ class FlatTypeDriver(api.TypeDriver):
|
|||||||
physical_network = segment[api.PHYSICAL_NETWORK]
|
physical_network = segment[api.PHYSICAL_NETWORK]
|
||||||
with session.begin(subtransactions=True):
|
with session.begin(subtransactions=True):
|
||||||
try:
|
try:
|
||||||
alloc = (session.query(FlatAllocation).
|
|
||||||
filter_by(physical_network=physical_network).
|
|
||||||
with_lockmode('update').
|
|
||||||
one())
|
|
||||||
raise exc.FlatNetworkInUse(
|
|
||||||
physical_network=physical_network)
|
|
||||||
except sa.orm.exc.NoResultFound:
|
|
||||||
LOG.debug(_("Reserving flat network on physical "
|
LOG.debug(_("Reserving flat network on physical "
|
||||||
"network %s"), physical_network)
|
"network %s"), physical_network)
|
||||||
alloc = FlatAllocation(physical_network=physical_network)
|
alloc = FlatAllocation(physical_network=physical_network)
|
||||||
session.add(alloc)
|
alloc.save(session)
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
raise exc.FlatNetworkInUse(
|
||||||
|
physical_network=physical_network)
|
||||||
|
return segment
|
||||||
|
|
||||||
def allocate_tenant_segment(self, session):
|
def allocate_tenant_segment(self, session):
|
||||||
# Tenant flat networks are not supported.
|
# Tenant flat networks are not supported.
|
||||||
|
@ -91,6 +91,7 @@ class GreTypeDriver(type_tunnel.TunnelTypeDriver):
|
|||||||
alloc = GreAllocation(gre_id=segmentation_id)
|
alloc = GreAllocation(gre_id=segmentation_id)
|
||||||
alloc.allocated = True
|
alloc.allocated = True
|
||||||
session.add(alloc)
|
session.add(alloc)
|
||||||
|
return segment
|
||||||
|
|
||||||
def allocate_tenant_segment(self, session):
|
def allocate_tenant_segment(self, session):
|
||||||
with session.begin(subtransactions=True):
|
with session.begin(subtransactions=True):
|
||||||
|
@ -48,7 +48,7 @@ class LocalTypeDriver(api.TypeDriver):
|
|||||||
|
|
||||||
def reserve_provider_segment(self, session, segment):
|
def reserve_provider_segment(self, session, segment):
|
||||||
# No resources to reserve
|
# No resources to reserve
|
||||||
pass
|
return segment
|
||||||
|
|
||||||
def allocate_tenant_segment(self, session):
|
def allocate_tenant_segment(self, session):
|
||||||
# No resources to allocate
|
# No resources to allocate
|
||||||
|
@ -28,6 +28,7 @@ from neutron.openstack.common import log
|
|||||||
from neutron.plugins.common import constants as p_const
|
from neutron.plugins.common import constants as p_const
|
||||||
from neutron.plugins.common import utils as plugin_utils
|
from neutron.plugins.common import utils as plugin_utils
|
||||||
from neutron.plugins.ml2 import driver_api as api
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
|
from neutron.plugins.ml2.drivers import helpers
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ class VlanAllocation(model_base.BASEV2):
|
|||||||
allocated = sa.Column(sa.Boolean, nullable=False)
|
allocated = sa.Column(sa.Boolean, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class VlanTypeDriver(api.TypeDriver):
|
class VlanTypeDriver(helpers.TypeDriverHelper):
|
||||||
"""Manage state for VLAN networks with ML2.
|
"""Manage state for VLAN networks with ML2.
|
||||||
|
|
||||||
The VlanTypeDriver implements the 'vlan' network_type. VLAN
|
The VlanTypeDriver implements the 'vlan' network_type. VLAN
|
||||||
@ -79,6 +80,7 @@ class VlanTypeDriver(api.TypeDriver):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super(VlanTypeDriver, self).__init__(VlanAllocation)
|
||||||
self._parse_network_vlan_ranges()
|
self._parse_network_vlan_ranges()
|
||||||
|
|
||||||
def _parse_network_vlan_ranges(self):
|
def _parse_network_vlan_ranges(self):
|
||||||
@ -160,25 +162,27 @@ class VlanTypeDriver(api.TypeDriver):
|
|||||||
self._sync_vlan_allocations()
|
self._sync_vlan_allocations()
|
||||||
LOG.info(_("VlanTypeDriver initialization complete"))
|
LOG.info(_("VlanTypeDriver initialization complete"))
|
||||||
|
|
||||||
|
def is_partial_segment(self, segment):
|
||||||
|
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)
|
||||||
if not physical_network:
|
|
||||||
msg = _("physical_network required for VLAN provider network")
|
|
||||||
raise exc.InvalidInput(error_message=msg)
|
|
||||||
if physical_network not in self.network_vlan_ranges:
|
|
||||||
msg = (_("physical_network '%s' unknown for VLAN provider network")
|
|
||||||
% physical_network)
|
|
||||||
raise exc.InvalidInput(error_message=msg)
|
|
||||||
|
|
||||||
segmentation_id = segment.get(api.SEGMENTATION_ID)
|
segmentation_id = segment.get(api.SEGMENTATION_ID)
|
||||||
if segmentation_id is None:
|
if physical_network:
|
||||||
msg = _("segmentation_id required for VLAN provider network")
|
if physical_network not in self.network_vlan_ranges:
|
||||||
raise exc.InvalidInput(error_message=msg)
|
msg = (_("physical_network '%s' unknown "
|
||||||
if not utils.is_valid_vlan_tag(segmentation_id):
|
" for VLAN provider network") % physical_network)
|
||||||
msg = (_("segmentation_id out of range (%(min)s through "
|
raise exc.InvalidInput(error_message=msg)
|
||||||
"%(max)s)") %
|
if segmentation_id:
|
||||||
{'min': q_const.MIN_VLAN_TAG,
|
if not utils.is_valid_vlan_tag(segmentation_id):
|
||||||
'max': q_const.MAX_VLAN_TAG})
|
msg = (_("segmentation_id out of range (%(min)s through "
|
||||||
|
"%(max)s)") %
|
||||||
|
{'min': q_const.MIN_VLAN_TAG,
|
||||||
|
'max': q_const.MAX_VLAN_TAG})
|
||||||
|
raise exc.InvalidInput(error_message=msg)
|
||||||
|
elif segmentation_id:
|
||||||
|
msg = _("segmentation_id requires physical_network for VLAN "
|
||||||
|
"provider network")
|
||||||
raise exc.InvalidInput(error_message=msg)
|
raise exc.InvalidInput(error_message=msg)
|
||||||
|
|
||||||
for key, value in segment.items():
|
for key, value in segment.items():
|
||||||
@ -189,48 +193,36 @@ class VlanTypeDriver(api.TypeDriver):
|
|||||||
raise exc.InvalidInput(error_message=msg)
|
raise exc.InvalidInput(error_message=msg)
|
||||||
|
|
||||||
def reserve_provider_segment(self, session, segment):
|
def reserve_provider_segment(self, session, segment):
|
||||||
physical_network = segment[api.PHYSICAL_NETWORK]
|
filters = {}
|
||||||
vlan_id = segment[api.SEGMENTATION_ID]
|
physical_network = segment.get(api.PHYSICAL_NETWORK)
|
||||||
with session.begin(subtransactions=True):
|
if physical_network is not None:
|
||||||
try:
|
filters['physical_network'] = physical_network
|
||||||
alloc = (session.query(VlanAllocation).
|
vlan_id = segment.get(api.SEGMENTATION_ID)
|
||||||
filter_by(physical_network=physical_network,
|
if vlan_id is not None:
|
||||||
vlan_id=vlan_id).
|
filters['vlan_id'] = vlan_id
|
||||||
with_lockmode('update').
|
|
||||||
one())
|
if self.is_partial_segment(segment):
|
||||||
if alloc.allocated:
|
alloc = self.allocate_partially_specified_segment(
|
||||||
raise exc.VlanIdInUse(vlan_id=vlan_id,
|
session, **filters)
|
||||||
physical_network=physical_network)
|
if not alloc:
|
||||||
LOG.debug(_("Reserving specific vlan %(vlan_id)s on physical "
|
raise exc.NoNetworkAvailable
|
||||||
"network %(physical_network)s from pool"),
|
else:
|
||||||
{'vlan_id': vlan_id,
|
alloc = self.allocate_fully_specified_segment(
|
||||||
'physical_network': physical_network})
|
session, **filters)
|
||||||
alloc.allocated = True
|
if not alloc:
|
||||||
except sa.orm.exc.NoResultFound:
|
raise exc.VlanIdInUse(**filters)
|
||||||
LOG.debug(_("Reserving specific vlan %(vlan_id)s on physical "
|
|
||||||
"network %(physical_network)s outside pool"),
|
return {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
||||||
{'vlan_id': vlan_id,
|
api.PHYSICAL_NETWORK: alloc.physical_network,
|
||||||
'physical_network': physical_network})
|
api.SEGMENTATION_ID: alloc.vlan_id}
|
||||||
alloc = VlanAllocation(physical_network=physical_network,
|
|
||||||
vlan_id=vlan_id,
|
|
||||||
allocated=True)
|
|
||||||
session.add(alloc)
|
|
||||||
|
|
||||||
def allocate_tenant_segment(self, session):
|
def allocate_tenant_segment(self, session):
|
||||||
with session.begin(subtransactions=True):
|
alloc = self.allocate_partially_specified_segment(session)
|
||||||
alloc = (session.query(VlanAllocation).
|
if not alloc:
|
||||||
filter_by(allocated=False).
|
return
|
||||||
with_lockmode('update').
|
return {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
||||||
first())
|
api.PHYSICAL_NETWORK: alloc.physical_network,
|
||||||
if alloc:
|
api.SEGMENTATION_ID: alloc.vlan_id}
|
||||||
LOG.debug(_("Allocating vlan %(vlan_id)s on physical network "
|
|
||||||
"%(physical_network)s from pool"),
|
|
||||||
{'vlan_id': alloc.vlan_id,
|
|
||||||
'physical_network': alloc.physical_network})
|
|
||||||
alloc.allocated = True
|
|
||||||
return {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
||||||
api.PHYSICAL_NETWORK: alloc.physical_network,
|
|
||||||
api.SEGMENTATION_ID: alloc.vlan_id}
|
|
||||||
|
|
||||||
def release_segment(self, session, segment):
|
def release_segment(self, session, segment):
|
||||||
physical_network = segment[api.PHYSICAL_NETWORK]
|
physical_network = segment[api.PHYSICAL_NETWORK]
|
||||||
|
@ -99,6 +99,7 @@ class VxlanTypeDriver(type_tunnel.TunnelTypeDriver):
|
|||||||
alloc = VxlanAllocation(vxlan_vni=segmentation_id)
|
alloc = VxlanAllocation(vxlan_vni=segmentation_id)
|
||||||
alloc.allocated = True
|
alloc.allocated = True
|
||||||
session.add(alloc)
|
session.add(alloc)
|
||||||
|
return segment
|
||||||
|
|
||||||
def allocate_tenant_segment(self, session):
|
def allocate_tenant_segment(self, session):
|
||||||
with session.begin(subtransactions=True):
|
with session.begin(subtransactions=True):
|
||||||
|
@ -85,7 +85,7 @@ class TypeManager(stevedore.named.NamedExtensionManager):
|
|||||||
def reserve_provider_segment(self, session, segment):
|
def reserve_provider_segment(self, session, segment):
|
||||||
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)
|
||||||
driver.obj.reserve_provider_segment(session, segment)
|
return driver.obj.reserve_provider_segment(session, segment)
|
||||||
|
|
||||||
def allocate_tenant_segment(self, session):
|
def allocate_tenant_segment(self, session):
|
||||||
for network_type in self.tenant_network_types:
|
for network_type in self.tenant_network_types:
|
||||||
|
@ -378,8 +378,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
# to TypeManager.
|
# to TypeManager.
|
||||||
if segments:
|
if segments:
|
||||||
for segment in segments:
|
for segment in segments:
|
||||||
self.type_manager.reserve_provider_segment(session,
|
segment = self.type_manager.reserve_provider_segment(
|
||||||
segment)
|
session, segment)
|
||||||
db.add_network_segment(session, network_id, segment)
|
db.add_network_segment(session, network_id, segment)
|
||||||
else:
|
else:
|
||||||
segment = self.type_manager.allocate_tenant_segment(session)
|
segment = self.type_manager.allocate_tenant_segment(session)
|
||||||
|
141
neutron/tests/unit/ml2/test_helpers.py
Normal file
141
neutron/tests/unit/ml2/test_helpers.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# Copyright (c) 2014 Thales Services SAS
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from sqlalchemy.orm import query
|
||||||
|
|
||||||
|
from neutron.common import exceptions as exc
|
||||||
|
import neutron.db.api as db
|
||||||
|
from neutron.plugins.ml2.drivers import helpers
|
||||||
|
from neutron.plugins.ml2.drivers import type_vlan
|
||||||
|
from neutron.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
TENANT_NET = 'phys_net2'
|
||||||
|
VLAN_MIN = 200
|
||||||
|
VLAN_MAX = 209
|
||||||
|
VLAN_OUTSIDE = 100
|
||||||
|
NETWORK_VLAN_RANGES = {
|
||||||
|
TENANT_NET: [(VLAN_MIN, VLAN_MAX)],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class HelpersTest(base.BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(HelpersTest, self).setUp()
|
||||||
|
db.configure_db()
|
||||||
|
self.driver = type_vlan.VlanTypeDriver()
|
||||||
|
self.driver.network_vlan_ranges = NETWORK_VLAN_RANGES
|
||||||
|
self.driver._sync_vlan_allocations()
|
||||||
|
self.session = db.get_session()
|
||||||
|
self.addCleanup(db.clear_db)
|
||||||
|
|
||||||
|
def check_raw_segment(self, expected, observed):
|
||||||
|
for key, value in expected.items():
|
||||||
|
self.assertEqual(value, observed[key])
|
||||||
|
|
||||||
|
def test_primary_keys(self):
|
||||||
|
self.assertEqual(set(['physical_network', 'vlan_id']),
|
||||||
|
self.driver.primary_keys)
|
||||||
|
|
||||||
|
def test_allocate_specific_unallocated_segment_in_pools(self):
|
||||||
|
expected = dict(physical_network=TENANT_NET, vlan_id=VLAN_MIN)
|
||||||
|
observed = self.driver.allocate_fully_specified_segment(self.session,
|
||||||
|
**expected)
|
||||||
|
self.check_raw_segment(expected, observed)
|
||||||
|
|
||||||
|
def test_allocate_specific_allocated_segment_in_pools(self):
|
||||||
|
raw_segment = dict(physical_network=TENANT_NET, vlan_id=VLAN_MIN)
|
||||||
|
self.driver.allocate_fully_specified_segment(self.session,
|
||||||
|
**raw_segment)
|
||||||
|
observed = self.driver.allocate_fully_specified_segment(self.session,
|
||||||
|
**raw_segment)
|
||||||
|
self.assertIsNone(observed)
|
||||||
|
|
||||||
|
def test_allocate_specific_finally_allocated_segment_in_pools(self):
|
||||||
|
# Test case: allocate a specific unallocated segment in pools but
|
||||||
|
# the segment is allocated concurrently between select and update
|
||||||
|
|
||||||
|
raw_segment = dict(physical_network=TENANT_NET, vlan_id=VLAN_MIN)
|
||||||
|
with mock.patch.object(query.Query, 'update', return_value=0):
|
||||||
|
observed = self.driver.allocate_fully_specified_segment(
|
||||||
|
self.session, **raw_segment)
|
||||||
|
self.assertIsNone(observed)
|
||||||
|
|
||||||
|
def test_allocate_specific_unallocated_segment_outside_pools(self):
|
||||||
|
expected = dict(physical_network=TENANT_NET, vlan_id=VLAN_OUTSIDE)
|
||||||
|
observed = self.driver.allocate_fully_specified_segment(self.session,
|
||||||
|
**expected)
|
||||||
|
self.check_raw_segment(expected, observed)
|
||||||
|
|
||||||
|
def test_allocate_specific_allocated_segment_outside_pools(self):
|
||||||
|
raw_segment = dict(physical_network=TENANT_NET, vlan_id=VLAN_OUTSIDE)
|
||||||
|
self.driver.allocate_fully_specified_segment(self.session,
|
||||||
|
**raw_segment)
|
||||||
|
observed = self.driver.allocate_fully_specified_segment(self.session,
|
||||||
|
**raw_segment)
|
||||||
|
self.assertIsNone(observed)
|
||||||
|
|
||||||
|
def test_allocate_specific_finally_unallocated_segment_outside_pools(self):
|
||||||
|
# Test case: allocate a specific allocated segment in pools but
|
||||||
|
# the segment is concurrently unallocated after select or update
|
||||||
|
|
||||||
|
expected = dict(physical_network=TENANT_NET, vlan_id=VLAN_MIN)
|
||||||
|
with mock.patch.object(self.driver.model, 'save'):
|
||||||
|
observed = self.driver.allocate_fully_specified_segment(
|
||||||
|
self.session, **expected)
|
||||||
|
self.check_raw_segment(expected, observed)
|
||||||
|
|
||||||
|
def test_allocate_partial_segment_without_filters(self):
|
||||||
|
expected = dict(physical_network=TENANT_NET)
|
||||||
|
observed = self.driver.allocate_partially_specified_segment(
|
||||||
|
self.session)
|
||||||
|
self.check_raw_segment(expected, observed)
|
||||||
|
|
||||||
|
def test_allocate_partial_segment_with_filter(self):
|
||||||
|
expected = dict(physical_network=TENANT_NET)
|
||||||
|
observed = self.driver.allocate_partially_specified_segment(
|
||||||
|
self.session, **expected)
|
||||||
|
self.check_raw_segment(expected, observed)
|
||||||
|
|
||||||
|
def test_allocate_partial_segment_no_resource_available(self):
|
||||||
|
for i in range(VLAN_MIN, VLAN_MAX + 1):
|
||||||
|
self.driver.allocate_partially_specified_segment(self.session)
|
||||||
|
observed = self.driver.allocate_partially_specified_segment(
|
||||||
|
self.session)
|
||||||
|
self.assertIsNone(observed)
|
||||||
|
|
||||||
|
def test_allocate_partial_segment_outside_pools(self):
|
||||||
|
raw_segment = dict(physical_network='other_phys_net')
|
||||||
|
observed = self.driver.allocate_partially_specified_segment(
|
||||||
|
self.session, **raw_segment)
|
||||||
|
self.assertIsNone(observed)
|
||||||
|
|
||||||
|
def test_allocate_partial_segment_first_attempt_fails(self):
|
||||||
|
expected = dict(physical_network=TENANT_NET)
|
||||||
|
with mock.patch.object(query.Query, 'update', side_effect=[0, 1]):
|
||||||
|
observed = self.driver.allocate_partially_specified_segment(
|
||||||
|
self.session, **expected)
|
||||||
|
self.check_raw_segment(expected, observed)
|
||||||
|
|
||||||
|
def test_allocate_partial_segment_all_attempts_fail(self):
|
||||||
|
with mock.patch.object(query.Query, 'update', return_value=0):
|
||||||
|
with mock.patch.object(helpers.LOG, 'warning') as log_warning:
|
||||||
|
self.assertRaises(
|
||||||
|
exc.NoNetworkFoundInMaximumAllowedAttempts,
|
||||||
|
self.driver.allocate_partially_specified_segment,
|
||||||
|
self.session)
|
||||||
|
log_warning.assert_called_once_with(mock.ANY, mock.ANY)
|
@ -85,8 +85,8 @@ class FlatTypeTest(base.BaseTestCase):
|
|||||||
def test_reserve_provider_segment(self):
|
def test_reserve_provider_segment(self):
|
||||||
segment = {api.NETWORK_TYPE: p_const.TYPE_FLAT,
|
segment = {api.NETWORK_TYPE: p_const.TYPE_FLAT,
|
||||||
api.PHYSICAL_NETWORK: 'flat_net1'}
|
api.PHYSICAL_NETWORK: 'flat_net1'}
|
||||||
self.driver.reserve_provider_segment(self.session, segment)
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
alloc = self._get_allocation(self.session, segment)
|
alloc = self._get_allocation(self.session, observed)
|
||||||
self.assertEqual(segment[api.PHYSICAL_NETWORK], alloc.physical_network)
|
self.assertEqual(segment[api.PHYSICAL_NETWORK], alloc.physical_network)
|
||||||
|
|
||||||
def test_release_segment(self):
|
def test_release_segment(self):
|
||||||
|
@ -112,9 +112,9 @@ class GreTypeTest(base.BaseTestCase):
|
|||||||
segment = {api.NETWORK_TYPE: 'gre',
|
segment = {api.NETWORK_TYPE: 'gre',
|
||||||
api.PHYSICAL_NETWORK: 'None',
|
api.PHYSICAL_NETWORK: 'None',
|
||||||
api.SEGMENTATION_ID: 101}
|
api.SEGMENTATION_ID: 101}
|
||||||
self.driver.reserve_provider_segment(self.session, segment)
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
alloc = self.driver.get_gre_allocation(self.session,
|
alloc = self.driver.get_gre_allocation(self.session,
|
||||||
segment[api.SEGMENTATION_ID])
|
observed[api.SEGMENTATION_ID])
|
||||||
self.assertTrue(alloc.allocated)
|
self.assertTrue(alloc.allocated)
|
||||||
|
|
||||||
with testtools.ExpectedException(exc.TunnelIdInUse):
|
with testtools.ExpectedException(exc.TunnelIdInUse):
|
||||||
@ -122,18 +122,18 @@ class GreTypeTest(base.BaseTestCase):
|
|||||||
|
|
||||||
self.driver.release_segment(self.session, segment)
|
self.driver.release_segment(self.session, segment)
|
||||||
alloc = self.driver.get_gre_allocation(self.session,
|
alloc = self.driver.get_gre_allocation(self.session,
|
||||||
segment[api.SEGMENTATION_ID])
|
observed[api.SEGMENTATION_ID])
|
||||||
self.assertFalse(alloc.allocated)
|
self.assertFalse(alloc.allocated)
|
||||||
|
|
||||||
segment[api.SEGMENTATION_ID] = 1000
|
segment[api.SEGMENTATION_ID] = 1000
|
||||||
self.driver.reserve_provider_segment(self.session, segment)
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
alloc = self.driver.get_gre_allocation(self.session,
|
alloc = self.driver.get_gre_allocation(self.session,
|
||||||
segment[api.SEGMENTATION_ID])
|
observed[api.SEGMENTATION_ID])
|
||||||
self.assertTrue(alloc.allocated)
|
self.assertTrue(alloc.allocated)
|
||||||
|
|
||||||
self.driver.release_segment(self.session, segment)
|
self.driver.release_segment(self.session, segment)
|
||||||
alloc = self.driver.get_gre_allocation(self.session,
|
alloc = self.driver.get_gre_allocation(self.session,
|
||||||
segment[api.SEGMENTATION_ID])
|
observed[api.SEGMENTATION_ID])
|
||||||
self.assertIsNone(alloc)
|
self.assertIsNone(alloc)
|
||||||
|
|
||||||
def test_allocate_tenant_segment(self):
|
def test_allocate_tenant_segment(self):
|
||||||
|
@ -47,8 +47,13 @@ class LocalTypeTest(base.BaseTestCase):
|
|||||||
|
|
||||||
def test_reserve_provider_segment(self):
|
def test_reserve_provider_segment(self):
|
||||||
segment = {api.NETWORK_TYPE: p_const.TYPE_LOCAL}
|
segment = {api.NETWORK_TYPE: p_const.TYPE_LOCAL}
|
||||||
self.driver.reserve_provider_segment(self.session, segment)
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
self.driver.release_segment(self.session, segment)
|
self.assertEqual(segment, observed)
|
||||||
|
|
||||||
|
def test_release_provider_segment(self):
|
||||||
|
segment = {api.NETWORK_TYPE: p_const.TYPE_LOCAL}
|
||||||
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
|
self.driver.release_segment(self.session, observed)
|
||||||
|
|
||||||
def test_allocate_tenant_segment(self):
|
def test_allocate_tenant_segment(self):
|
||||||
expected = {api.NETWORK_TYPE: p_const.TYPE_LOCAL}
|
expected = {api.NETWORK_TYPE: p_const.TYPE_LOCAL}
|
||||||
|
@ -53,12 +53,31 @@ class VlanTypeTest(base.BaseTestCase):
|
|||||||
physical_network=segment[api.PHYSICAL_NETWORK],
|
physical_network=segment[api.PHYSICAL_NETWORK],
|
||||||
vlan_id=segment[api.SEGMENTATION_ID]).first()
|
vlan_id=segment[api.SEGMENTATION_ID]).first()
|
||||||
|
|
||||||
|
def test_partial_segment_is_partial_segment(self):
|
||||||
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN}
|
||||||
|
self.assertTrue(self.driver.is_partial_segment(segment))
|
||||||
|
|
||||||
|
def test_specific_segment_is_not_partial_segment(self):
|
||||||
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
||||||
|
api.PHYSICAL_NETWORK: PROVIDER_NET,
|
||||||
|
api.SEGMENTATION_ID: 1}
|
||||||
|
self.assertFalse(self.driver.is_partial_segment(segment))
|
||||||
|
|
||||||
def test_validate_provider_segment(self):
|
def test_validate_provider_segment(self):
|
||||||
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
||||||
api.PHYSICAL_NETWORK: PROVIDER_NET,
|
api.PHYSICAL_NETWORK: PROVIDER_NET,
|
||||||
api.SEGMENTATION_ID: 1}
|
api.SEGMENTATION_ID: 1}
|
||||||
self.assertIsNone(self.driver.validate_provider_segment(segment))
|
self.assertIsNone(self.driver.validate_provider_segment(segment))
|
||||||
|
|
||||||
|
def test_validate_provider_segment_without_segmentation_id(self):
|
||||||
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
||||||
|
api.PHYSICAL_NETWORK: TENANT_NET}
|
||||||
|
self.driver.validate_provider_segment(segment)
|
||||||
|
|
||||||
|
def test_validate_provider_segment_without_physical_network(self):
|
||||||
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN}
|
||||||
|
self.driver.validate_provider_segment(segment)
|
||||||
|
|
||||||
def test_validate_provider_segment_with_missing_physical_network(self):
|
def test_validate_provider_segment_with_missing_physical_network(self):
|
||||||
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
||||||
api.SEGMENTATION_ID: 1}
|
api.SEGMENTATION_ID: 1}
|
||||||
@ -66,13 +85,6 @@ class VlanTypeTest(base.BaseTestCase):
|
|||||||
self.driver.validate_provider_segment,
|
self.driver.validate_provider_segment,
|
||||||
segment)
|
segment)
|
||||||
|
|
||||||
def test_validate_provider_segment_with_missing_segmentation_id(self):
|
|
||||||
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
||||||
api.PHYSICAL_NETWORK: PROVIDER_NET}
|
|
||||||
self.assertRaises(exc.InvalidInput,
|
|
||||||
self.driver.validate_provider_segment,
|
|
||||||
segment)
|
|
||||||
|
|
||||||
def test_validate_provider_segment_with_invalid_physical_network(self):
|
def test_validate_provider_segment_with_invalid_physical_network(self):
|
||||||
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
||||||
api.PHYSICAL_NETWORK: 'other_phys_net',
|
api.PHYSICAL_NETWORK: 'other_phys_net',
|
||||||
@ -129,19 +141,19 @@ class VlanTypeTest(base.BaseTestCase):
|
|||||||
api.SEGMENTATION_ID: 101}
|
api.SEGMENTATION_ID: 101}
|
||||||
alloc = self._get_allocation(self.session, segment)
|
alloc = self._get_allocation(self.session, segment)
|
||||||
self.assertIsNone(alloc)
|
self.assertIsNone(alloc)
|
||||||
self.driver.reserve_provider_segment(self.session, segment)
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
alloc = self._get_allocation(self.session, segment)
|
alloc = self._get_allocation(self.session, observed)
|
||||||
self.assertTrue(alloc.allocated)
|
self.assertTrue(alloc.allocated)
|
||||||
|
|
||||||
def test_reserve_provider_segment_already_allocated(self):
|
def test_reserve_provider_segment_already_allocated(self):
|
||||||
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
||||||
api.PHYSICAL_NETWORK: PROVIDER_NET,
|
api.PHYSICAL_NETWORK: PROVIDER_NET,
|
||||||
api.SEGMENTATION_ID: 101}
|
api.SEGMENTATION_ID: 101}
|
||||||
self.driver.reserve_provider_segment(self.session, segment)
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
self.assertRaises(exc.VlanIdInUse,
|
self.assertRaises(exc.VlanIdInUse,
|
||||||
self.driver.reserve_provider_segment,
|
self.driver.reserve_provider_segment,
|
||||||
self.session,
|
self.session,
|
||||||
segment)
|
observed)
|
||||||
|
|
||||||
def test_reserve_provider_segment_in_tenant_pools(self):
|
def test_reserve_provider_segment_in_tenant_pools(self):
|
||||||
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
||||||
@ -149,10 +161,39 @@ class VlanTypeTest(base.BaseTestCase):
|
|||||||
api.SEGMENTATION_ID: VLAN_MIN}
|
api.SEGMENTATION_ID: VLAN_MIN}
|
||||||
alloc = self._get_allocation(self.session, segment)
|
alloc = self._get_allocation(self.session, segment)
|
||||||
self.assertFalse(alloc.allocated)
|
self.assertFalse(alloc.allocated)
|
||||||
self.driver.reserve_provider_segment(self.session, segment)
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
alloc = self._get_allocation(self.session, segment)
|
alloc = self._get_allocation(self.session, observed)
|
||||||
self.assertTrue(alloc.allocated)
|
self.assertTrue(alloc.allocated)
|
||||||
|
|
||||||
|
def test_reserve_provider_segment_without_segmentation_id(self):
|
||||||
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
||||||
|
api.PHYSICAL_NETWORK: TENANT_NET}
|
||||||
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
|
alloc = self._get_allocation(self.session, observed)
|
||||||
|
self.assertTrue(alloc.allocated)
|
||||||
|
vlan_id = observed[api.SEGMENTATION_ID]
|
||||||
|
self.assertThat(vlan_id, matchers.GreaterThan(VLAN_MIN - 1))
|
||||||
|
self.assertThat(vlan_id, matchers.LessThan(VLAN_MAX + 1))
|
||||||
|
|
||||||
|
def test_reserve_provider_segment_without_physical_network(self):
|
||||||
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN}
|
||||||
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
|
alloc = self._get_allocation(self.session, observed)
|
||||||
|
self.assertTrue(alloc.allocated)
|
||||||
|
vlan_id = observed[api.SEGMENTATION_ID]
|
||||||
|
self.assertThat(vlan_id, matchers.GreaterThan(VLAN_MIN - 1))
|
||||||
|
self.assertThat(vlan_id, matchers.LessThan(VLAN_MAX + 1))
|
||||||
|
self.assertEqual(TENANT_NET, observed[api.PHYSICAL_NETWORK])
|
||||||
|
|
||||||
|
def test_reserve_provider_segment_all_allocateds(self):
|
||||||
|
for __ in range(VLAN_MIN, VLAN_MAX + 1):
|
||||||
|
self.driver.allocate_tenant_segment(self.session)
|
||||||
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN}
|
||||||
|
self.assertRaises(exc.NoNetworkAvailable,
|
||||||
|
self.driver.reserve_provider_segment,
|
||||||
|
self.session,
|
||||||
|
segment)
|
||||||
|
|
||||||
def test_allocate_tenant_segment(self):
|
def test_allocate_tenant_segment(self):
|
||||||
for __ in range(VLAN_MIN, VLAN_MAX + 1):
|
for __ in range(VLAN_MIN, VLAN_MAX + 1):
|
||||||
segment = self.driver.allocate_tenant_segment(self.session)
|
segment = self.driver.allocate_tenant_segment(self.session)
|
||||||
|
@ -120,9 +120,9 @@ class VxlanTypeTest(base.BaseTestCase):
|
|||||||
segment = {api.NETWORK_TYPE: 'vxlan',
|
segment = {api.NETWORK_TYPE: 'vxlan',
|
||||||
api.PHYSICAL_NETWORK: 'None',
|
api.PHYSICAL_NETWORK: 'None',
|
||||||
api.SEGMENTATION_ID: 101}
|
api.SEGMENTATION_ID: 101}
|
||||||
self.driver.reserve_provider_segment(self.session, segment)
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
alloc = self.driver.get_vxlan_allocation(self.session,
|
alloc = self.driver.get_vxlan_allocation(self.session,
|
||||||
segment[api.SEGMENTATION_ID])
|
observed[api.SEGMENTATION_ID])
|
||||||
self.assertTrue(alloc.allocated)
|
self.assertTrue(alloc.allocated)
|
||||||
|
|
||||||
with testtools.ExpectedException(exc.TunnelIdInUse):
|
with testtools.ExpectedException(exc.TunnelIdInUse):
|
||||||
@ -130,18 +130,18 @@ class VxlanTypeTest(base.BaseTestCase):
|
|||||||
|
|
||||||
self.driver.release_segment(self.session, segment)
|
self.driver.release_segment(self.session, segment)
|
||||||
alloc = self.driver.get_vxlan_allocation(self.session,
|
alloc = self.driver.get_vxlan_allocation(self.session,
|
||||||
segment[api.SEGMENTATION_ID])
|
observed[api.SEGMENTATION_ID])
|
||||||
self.assertFalse(alloc.allocated)
|
self.assertFalse(alloc.allocated)
|
||||||
|
|
||||||
segment[api.SEGMENTATION_ID] = 1000
|
segment[api.SEGMENTATION_ID] = 1000
|
||||||
self.driver.reserve_provider_segment(self.session, segment)
|
observed = self.driver.reserve_provider_segment(self.session, segment)
|
||||||
alloc = self.driver.get_vxlan_allocation(self.session,
|
alloc = self.driver.get_vxlan_allocation(self.session,
|
||||||
segment[api.SEGMENTATION_ID])
|
observed[api.SEGMENTATION_ID])
|
||||||
self.assertTrue(alloc.allocated)
|
self.assertTrue(alloc.allocated)
|
||||||
|
|
||||||
self.driver.release_segment(self.session, segment)
|
self.driver.release_segment(self.session, segment)
|
||||||
alloc = self.driver.get_vxlan_allocation(self.session,
|
alloc = self.driver.get_vxlan_allocation(self.session,
|
||||||
segment[api.SEGMENTATION_ID])
|
observed[api.SEGMENTATION_ID])
|
||||||
self.assertIsNone(alloc)
|
self.assertIsNone(alloc)
|
||||||
|
|
||||||
def test_allocate_tenant_segment(self):
|
def test_allocate_tenant_segment(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user