From b3202c3283597de031572e0ede082237487d8110 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Mon, 16 Jun 2014 22:43:13 +0200 Subject: [PATCH] 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 --- neutron/common/exceptions.py | 5 + neutron/plugins/ml2/driver_api.py | 3 +- neutron/plugins/ml2/drivers/helpers.py | 140 +++++++++++++++++++++ neutron/plugins/ml2/drivers/type_flat.py | 14 +-- neutron/plugins/ml2/drivers/type_gre.py | 1 + neutron/plugins/ml2/drivers/type_local.py | 2 +- neutron/plugins/ml2/drivers/type_vlan.py | 106 ++++++++-------- neutron/plugins/ml2/drivers/type_vxlan.py | 1 + neutron/plugins/ml2/managers.py | 2 +- neutron/plugins/ml2/plugin.py | 4 +- neutron/tests/unit/ml2/test_helpers.py | 141 ++++++++++++++++++++++ neutron/tests/unit/ml2/test_type_flat.py | 4 +- neutron/tests/unit/ml2/test_type_gre.py | 12 +- neutron/tests/unit/ml2/test_type_local.py | 9 +- neutron/tests/unit/ml2/test_type_vlan.py | 67 ++++++++-- neutron/tests/unit/ml2/test_type_vxlan.py | 12 +- 16 files changed, 424 insertions(+), 99 deletions(-) create mode 100644 neutron/plugins/ml2/drivers/helpers.py create mode 100644 neutron/tests/unit/ml2/test_helpers.py 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):