diff --git a/neutron/common/exceptions.py b/neutron/common/exceptions.py index 7fa63affd..3124ea781 100644 --- a/neutron/common/exceptions.py +++ b/neutron/common/exceptions.py @@ -179,6 +179,11 @@ class NoNetworkAvailable(ResourceExhausted): "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): message = _("Subnet on port %(port_id)s does not match " "the requested subnet %(subnet_id)s") diff --git a/neutron/plugins/ml2/driver_api.py b/neutron/plugins/ml2/driver_api.py index 2384b0cf9..3b7e210ac 100644 --- a/neutron/plugins/ml2/driver_api.py +++ b/neutron/plugins/ml2/driver_api.py @@ -88,7 +88,8 @@ class TypeDriver(object): """Reserve resource associated with a provider network segment. :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 type-specific resource for a provider network segment. The diff --git a/neutron/plugins/ml2/drivers/helpers.py b/neutron/plugins/ml2/drivers/helpers.py new file mode 100644 index 000000000..9e73a8b6c --- /dev/null +++ b/neutron/plugins/ml2/drivers/helpers.py @@ -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 diff --git a/neutron/plugins/ml2/drivers/type_flat.py b/neutron/plugins/ml2/drivers/type_flat.py index 4597fe894..ec9c4edbf 100644 --- a/neutron/plugins/ml2/drivers/type_flat.py +++ b/neutron/plugins/ml2/drivers/type_flat.py @@ -14,6 +14,7 @@ # under the License. from oslo.config import cfg +from oslo.db import exception as db_exc import sqlalchemy as sa from neutron.common import exceptions as exc @@ -100,17 +101,14 @@ class FlatTypeDriver(api.TypeDriver): physical_network = segment[api.PHYSICAL_NETWORK] with session.begin(subtransactions=True): 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 " "network %s"), 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): # Tenant flat networks are not supported. diff --git a/neutron/plugins/ml2/drivers/type_gre.py b/neutron/plugins/ml2/drivers/type_gre.py index b9a00c0cb..1330a48f0 100644 --- a/neutron/plugins/ml2/drivers/type_gre.py +++ b/neutron/plugins/ml2/drivers/type_gre.py @@ -91,6 +91,7 @@ class GreTypeDriver(type_tunnel.TunnelTypeDriver): alloc = GreAllocation(gre_id=segmentation_id) alloc.allocated = True session.add(alloc) + return segment def allocate_tenant_segment(self, session): with session.begin(subtransactions=True): diff --git a/neutron/plugins/ml2/drivers/type_local.py b/neutron/plugins/ml2/drivers/type_local.py index e0281a245..df22302f9 100644 --- a/neutron/plugins/ml2/drivers/type_local.py +++ b/neutron/plugins/ml2/drivers/type_local.py @@ -48,7 +48,7 @@ class LocalTypeDriver(api.TypeDriver): def reserve_provider_segment(self, session, segment): # No resources to reserve - pass + return segment def allocate_tenant_segment(self, session): # No resources to allocate diff --git a/neutron/plugins/ml2/drivers/type_vlan.py b/neutron/plugins/ml2/drivers/type_vlan.py index c35ba3ce4..35cbace21 100644 --- a/neutron/plugins/ml2/drivers/type_vlan.py +++ b/neutron/plugins/ml2/drivers/type_vlan.py @@ -28,6 +28,7 @@ from neutron.openstack.common import log from neutron.plugins.common import constants as p_const from neutron.plugins.common import utils as plugin_utils from neutron.plugins.ml2 import driver_api as api +from neutron.plugins.ml2.drivers import helpers LOG = log.getLogger(__name__) @@ -67,7 +68,7 @@ class VlanAllocation(model_base.BASEV2): allocated = sa.Column(sa.Boolean, nullable=False) -class VlanTypeDriver(api.TypeDriver): +class VlanTypeDriver(helpers.TypeDriverHelper): """Manage state for VLAN networks with ML2. The VlanTypeDriver implements the 'vlan' network_type. VLAN @@ -79,6 +80,7 @@ class VlanTypeDriver(api.TypeDriver): """ def __init__(self): + super(VlanTypeDriver, self).__init__(VlanAllocation) self._parse_network_vlan_ranges() def _parse_network_vlan_ranges(self): @@ -160,25 +162,27 @@ class VlanTypeDriver(api.TypeDriver): self._sync_vlan_allocations() 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): 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) - if segmentation_id is None: - msg = _("segmentation_id required for VLAN provider network") - raise exc.InvalidInput(error_message=msg) - if not utils.is_valid_vlan_tag(segmentation_id): - msg = (_("segmentation_id out of range (%(min)s through " - "%(max)s)") % - {'min': q_const.MIN_VLAN_TAG, - 'max': q_const.MAX_VLAN_TAG}) + if physical_network: + 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) + if segmentation_id: + if not utils.is_valid_vlan_tag(segmentation_id): + 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) for key, value in segment.items(): @@ -189,48 +193,36 @@ class VlanTypeDriver(api.TypeDriver): raise exc.InvalidInput(error_message=msg) def reserve_provider_segment(self, session, segment): - physical_network = segment[api.PHYSICAL_NETWORK] - vlan_id = segment[api.SEGMENTATION_ID] - with session.begin(subtransactions=True): - try: - alloc = (session.query(VlanAllocation). - filter_by(physical_network=physical_network, - vlan_id=vlan_id). - with_lockmode('update'). - one()) - if alloc.allocated: - raise exc.VlanIdInUse(vlan_id=vlan_id, - physical_network=physical_network) - LOG.debug(_("Reserving specific vlan %(vlan_id)s on physical " - "network %(physical_network)s from pool"), - {'vlan_id': vlan_id, - 'physical_network': physical_network}) - alloc.allocated = True - except sa.orm.exc.NoResultFound: - LOG.debug(_("Reserving specific vlan %(vlan_id)s on physical " - "network %(physical_network)s outside pool"), - {'vlan_id': vlan_id, - 'physical_network': physical_network}) - alloc = VlanAllocation(physical_network=physical_network, - vlan_id=vlan_id, - allocated=True) - session.add(alloc) + filters = {} + physical_network = segment.get(api.PHYSICAL_NETWORK) + if physical_network is not None: + filters['physical_network'] = physical_network + vlan_id = segment.get(api.SEGMENTATION_ID) + if vlan_id is not None: + filters['vlan_id'] = vlan_id + + if self.is_partial_segment(segment): + alloc = self.allocate_partially_specified_segment( + session, **filters) + if not alloc: + raise exc.NoNetworkAvailable + else: + alloc = self.allocate_fully_specified_segment( + session, **filters) + if not alloc: + raise exc.VlanIdInUse(**filters) + + return {api.NETWORK_TYPE: p_const.TYPE_VLAN, + api.PHYSICAL_NETWORK: alloc.physical_network, + api.SEGMENTATION_ID: alloc.vlan_id} def allocate_tenant_segment(self, session): - with session.begin(subtransactions=True): - alloc = (session.query(VlanAllocation). - filter_by(allocated=False). - with_lockmode('update'). - first()) - if alloc: - 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} + alloc = self.allocate_partially_specified_segment(session) + if not alloc: + return + 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): physical_network = segment[api.PHYSICAL_NETWORK] diff --git a/neutron/plugins/ml2/drivers/type_vxlan.py b/neutron/plugins/ml2/drivers/type_vxlan.py index b5aeadbb6..918a9b6bc 100644 --- a/neutron/plugins/ml2/drivers/type_vxlan.py +++ b/neutron/plugins/ml2/drivers/type_vxlan.py @@ -99,6 +99,7 @@ class VxlanTypeDriver(type_tunnel.TunnelTypeDriver): alloc = VxlanAllocation(vxlan_vni=segmentation_id) alloc.allocated = True session.add(alloc) + return segment def allocate_tenant_segment(self, session): with session.begin(subtransactions=True): diff --git a/neutron/plugins/ml2/managers.py b/neutron/plugins/ml2/managers.py index 13df6732e..d7b64fa4e 100644 --- a/neutron/plugins/ml2/managers.py +++ b/neutron/plugins/ml2/managers.py @@ -85,7 +85,7 @@ class TypeManager(stevedore.named.NamedExtensionManager): def reserve_provider_segment(self, session, segment): network_type = segment.get(api.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): for network_type in self.tenant_network_types: diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 4a46bf548..c29114a84 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -378,8 +378,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, # to TypeManager. if segments: for segment in segments: - self.type_manager.reserve_provider_segment(session, - segment) + segment = self.type_manager.reserve_provider_segment( + session, segment) db.add_network_segment(session, network_id, segment) else: segment = self.type_manager.allocate_tenant_segment(session) diff --git a/neutron/tests/unit/ml2/test_helpers.py b/neutron/tests/unit/ml2/test_helpers.py new file mode 100644 index 000000000..24f8eea4e --- /dev/null +++ b/neutron/tests/unit/ml2/test_helpers.py @@ -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) diff --git a/neutron/tests/unit/ml2/test_type_flat.py b/neutron/tests/unit/ml2/test_type_flat.py index 759424e8f..35c3a4612 100644 --- a/neutron/tests/unit/ml2/test_type_flat.py +++ b/neutron/tests/unit/ml2/test_type_flat.py @@ -85,8 +85,8 @@ class FlatTypeTest(base.BaseTestCase): def test_reserve_provider_segment(self): segment = {api.NETWORK_TYPE: p_const.TYPE_FLAT, api.PHYSICAL_NETWORK: 'flat_net1'} - self.driver.reserve_provider_segment(self.session, segment) - alloc = self._get_allocation(self.session, segment) + observed = self.driver.reserve_provider_segment(self.session, segment) + alloc = self._get_allocation(self.session, observed) self.assertEqual(segment[api.PHYSICAL_NETWORK], alloc.physical_network) def test_release_segment(self): diff --git a/neutron/tests/unit/ml2/test_type_gre.py b/neutron/tests/unit/ml2/test_type_gre.py index 36420e2b6..b27fbdbbf 100644 --- a/neutron/tests/unit/ml2/test_type_gre.py +++ b/neutron/tests/unit/ml2/test_type_gre.py @@ -112,9 +112,9 @@ class GreTypeTest(base.BaseTestCase): segment = {api.NETWORK_TYPE: 'gre', api.PHYSICAL_NETWORK: 'None', 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, - segment[api.SEGMENTATION_ID]) + observed[api.SEGMENTATION_ID]) self.assertTrue(alloc.allocated) with testtools.ExpectedException(exc.TunnelIdInUse): @@ -122,18 +122,18 @@ class GreTypeTest(base.BaseTestCase): self.driver.release_segment(self.session, segment) alloc = self.driver.get_gre_allocation(self.session, - segment[api.SEGMENTATION_ID]) + observed[api.SEGMENTATION_ID]) self.assertFalse(alloc.allocated) 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, - segment[api.SEGMENTATION_ID]) + observed[api.SEGMENTATION_ID]) self.assertTrue(alloc.allocated) self.driver.release_segment(self.session, segment) alloc = self.driver.get_gre_allocation(self.session, - segment[api.SEGMENTATION_ID]) + observed[api.SEGMENTATION_ID]) self.assertIsNone(alloc) def test_allocate_tenant_segment(self): diff --git a/neutron/tests/unit/ml2/test_type_local.py b/neutron/tests/unit/ml2/test_type_local.py index 8d835bfd4..f36b85351 100644 --- a/neutron/tests/unit/ml2/test_type_local.py +++ b/neutron/tests/unit/ml2/test_type_local.py @@ -47,8 +47,13 @@ class LocalTypeTest(base.BaseTestCase): def test_reserve_provider_segment(self): segment = {api.NETWORK_TYPE: p_const.TYPE_LOCAL} - self.driver.reserve_provider_segment(self.session, segment) - self.driver.release_segment(self.session, segment) + observed = self.driver.reserve_provider_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): expected = {api.NETWORK_TYPE: p_const.TYPE_LOCAL} diff --git a/neutron/tests/unit/ml2/test_type_vlan.py b/neutron/tests/unit/ml2/test_type_vlan.py index deb86c0af..b36bc42e9 100644 --- a/neutron/tests/unit/ml2/test_type_vlan.py +++ b/neutron/tests/unit/ml2/test_type_vlan.py @@ -53,12 +53,31 @@ class VlanTypeTest(base.BaseTestCase): physical_network=segment[api.PHYSICAL_NETWORK], 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): segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN, api.PHYSICAL_NETWORK: PROVIDER_NET, api.SEGMENTATION_ID: 1} 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): segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN, api.SEGMENTATION_ID: 1} @@ -66,13 +85,6 @@ class VlanTypeTest(base.BaseTestCase): self.driver.validate_provider_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): segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN, api.PHYSICAL_NETWORK: 'other_phys_net', @@ -129,19 +141,19 @@ class VlanTypeTest(base.BaseTestCase): api.SEGMENTATION_ID: 101} alloc = self._get_allocation(self.session, segment) self.assertIsNone(alloc) - self.driver.reserve_provider_segment(self.session, segment) - alloc = self._get_allocation(self.session, segment) + observed = self.driver.reserve_provider_segment(self.session, segment) + alloc = self._get_allocation(self.session, observed) self.assertTrue(alloc.allocated) def test_reserve_provider_segment_already_allocated(self): segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN, api.PHYSICAL_NETWORK: PROVIDER_NET, 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.driver.reserve_provider_segment, self.session, - segment) + observed) def test_reserve_provider_segment_in_tenant_pools(self): segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN, @@ -149,10 +161,39 @@ class VlanTypeTest(base.BaseTestCase): api.SEGMENTATION_ID: VLAN_MIN} alloc = self._get_allocation(self.session, segment) self.assertFalse(alloc.allocated) - self.driver.reserve_provider_segment(self.session, segment) - alloc = self._get_allocation(self.session, segment) + observed = self.driver.reserve_provider_segment(self.session, segment) + alloc = self._get_allocation(self.session, observed) 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): for __ in range(VLAN_MIN, VLAN_MAX + 1): segment = self.driver.allocate_tenant_segment(self.session) diff --git a/neutron/tests/unit/ml2/test_type_vxlan.py b/neutron/tests/unit/ml2/test_type_vxlan.py index 1242df2ab..836feef17 100644 --- a/neutron/tests/unit/ml2/test_type_vxlan.py +++ b/neutron/tests/unit/ml2/test_type_vxlan.py @@ -120,9 +120,9 @@ class VxlanTypeTest(base.BaseTestCase): segment = {api.NETWORK_TYPE: 'vxlan', api.PHYSICAL_NETWORK: 'None', 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, - segment[api.SEGMENTATION_ID]) + observed[api.SEGMENTATION_ID]) self.assertTrue(alloc.allocated) with testtools.ExpectedException(exc.TunnelIdInUse): @@ -130,18 +130,18 @@ class VxlanTypeTest(base.BaseTestCase): self.driver.release_segment(self.session, segment) alloc = self.driver.get_vxlan_allocation(self.session, - segment[api.SEGMENTATION_ID]) + observed[api.SEGMENTATION_ID]) self.assertFalse(alloc.allocated) 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, - segment[api.SEGMENTATION_ID]) + observed[api.SEGMENTATION_ID]) self.assertTrue(alloc.allocated) self.driver.release_segment(self.session, segment) alloc = self.driver.get_vxlan_allocation(self.session, - segment[api.SEGMENTATION_ID]) + observed[api.SEGMENTATION_ID]) self.assertIsNone(alloc) def test_allocate_tenant_segment(self):