From c9fd72a8e1c6a0b936e0c6d00285c0b9764108d5 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Mon, 16 Jun 2014 22:03:14 +0200 Subject: [PATCH] Add partial specs support in ML2 for gre/vxlan 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 GRE/VXLAN provider networks. In practice, for GRE/VXLAN provider networks provider:segmentation_id choice 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: I720d7d04f6e3453145e888d9e4d9b5e381d0f7d4 --- neutron/plugins/ml2/drivers/type_gre.py | 56 +++++++++------------ neutron/plugins/ml2/drivers/type_tunnel.py | 9 ++-- neutron/plugins/ml2/drivers/type_vxlan.py | 56 +++++++++------------ neutron/tests/unit/ml2/test_type_gre.py | 57 ++++++++++++++++++++-- neutron/tests/unit/ml2/test_type_vxlan.py | 57 ++++++++++++++++++++-- 5 files changed, 157 insertions(+), 78 deletions(-) diff --git a/neutron/plugins/ml2/drivers/type_gre.py b/neutron/plugins/ml2/drivers/type_gre.py index 768d1fc35..9ade7db7b 100644 --- a/neutron/plugins/ml2/drivers/type_gre.py +++ b/neutron/plugins/ml2/drivers/type_gre.py @@ -25,6 +25,7 @@ from neutron.db import model_base from neutron.openstack.common import log from neutron.plugins.common import constants as p_const from neutron.plugins.ml2 import driver_api as api +from neutron.plugins.ml2.drivers import helpers from neutron.plugins.ml2.drivers import type_tunnel LOG = log.getLogger(__name__) @@ -60,7 +61,10 @@ class GreEndpoints(model_base.BASEV2): return "" % self.ip_address -class GreTypeDriver(type_tunnel.TunnelTypeDriver): +class GreTypeDriver(helpers.TypeDriverHelper, type_tunnel.TunnelTypeDriver): + + def __init__(self): + super(GreTypeDriver, self).__init__(GreAllocation) def get_type(self): return p_const.TYPE_GRE @@ -75,39 +79,27 @@ class GreTypeDriver(type_tunnel.TunnelTypeDriver): self._sync_gre_allocations() def reserve_provider_segment(self, session, segment): - segmentation_id = segment.get(api.SEGMENTATION_ID) - with session.begin(subtransactions=True): - try: - alloc = (session.query(GreAllocation). - filter_by(gre_id=segmentation_id). - with_lockmode('update'). - one()) - if alloc.allocated: - raise exc.TunnelIdInUse(tunnel_id=segmentation_id) - LOG.debug(_("Reserving specific gre tunnel %s from pool"), - segmentation_id) - alloc.allocated = True - except sa_exc.NoResultFound: - LOG.debug(_("Reserving specific gre tunnel %s outside pool"), - segmentation_id) - alloc = GreAllocation(gre_id=segmentation_id) - alloc.allocated = True - session.add(alloc) - return segment + if self.is_partial_segment(segment): + alloc = self.allocate_partially_specified_segment(session) + if not alloc: + raise exc.NoNetworkAvailable + else: + segmentation_id = segment.get(api.SEGMENTATION_ID) + alloc = self.allocate_fully_specified_segment( + session, gre_id=segmentation_id) + if not alloc: + raise exc.TunnelIdInUse(tunnel_id=segmentation_id) + return {api.NETWORK_TYPE: p_const.TYPE_GRE, + api.PHYSICAL_NETWORK: None, + api.SEGMENTATION_ID: alloc.gre_id} def allocate_tenant_segment(self, session): - with session.begin(subtransactions=True): - alloc = (session.query(GreAllocation). - filter_by(allocated=False). - with_lockmode('update'). - first()) - if alloc: - LOG.debug(_("Allocating gre tunnel id %(gre_id)s"), - {'gre_id': alloc.gre_id}) - alloc.allocated = True - return {api.NETWORK_TYPE: p_const.TYPE_GRE, - api.PHYSICAL_NETWORK: None, - api.SEGMENTATION_ID: alloc.gre_id} + alloc = self.allocate_partially_specified_segment(session) + if not alloc: + return + return {api.NETWORK_TYPE: p_const.TYPE_GRE, + api.PHYSICAL_NETWORK: None, + api.SEGMENTATION_ID: alloc.gre_id} def release_segment(self, session, segment): gre_id = segment[api.SEGMENTATION_ID] diff --git a/neutron/plugins/ml2/drivers/type_tunnel.py b/neutron/plugins/ml2/drivers/type_tunnel.py index fbc7110ea..17d529f5c 100644 --- a/neutron/plugins/ml2/drivers/type_tunnel.py +++ b/neutron/plugins/ml2/drivers/type_tunnel.py @@ -64,6 +64,9 @@ class TunnelTypeDriver(api.TypeDriver): LOG.info(_("%(type)s ID ranges: %(range)s"), {'type': tunnel_type, 'range': current_range}) + 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 physical_network: @@ -71,12 +74,6 @@ class TunnelTypeDriver(api.TypeDriver): "network") % segment.get(api.NETWORK_TYPE) raise exc.InvalidInput(error_message=msg) - segmentation_id = segment.get(api.SEGMENTATION_ID) - if not segmentation_id: - msg = _("segmentation_id required for %s provider " - "network") % segment.get(api.NETWORK_TYPE) - raise exc.InvalidInput(error_message=msg) - for key, value in segment.items(): if value and key not in [api.NETWORK_TYPE, api.SEGMENTATION_ID]: diff --git a/neutron/plugins/ml2/drivers/type_vxlan.py b/neutron/plugins/ml2/drivers/type_vxlan.py index d77aa1e70..9ddcf735d 100644 --- a/neutron/plugins/ml2/drivers/type_vxlan.py +++ b/neutron/plugins/ml2/drivers/type_vxlan.py @@ -25,6 +25,7 @@ from neutron.db import model_base from neutron.openstack.common import log from neutron.plugins.common import constants as p_const from neutron.plugins.ml2 import driver_api as api +from neutron.plugins.ml2.drivers import helpers from neutron.plugins.ml2.drivers import type_tunnel LOG = log.getLogger(__name__) @@ -68,7 +69,10 @@ class VxlanEndpoints(model_base.BASEV2): return "" % self.ip_address -class VxlanTypeDriver(type_tunnel.TunnelTypeDriver): +class VxlanTypeDriver(helpers.TypeDriverHelper, type_tunnel.TunnelTypeDriver): + + def __init__(self): + super(VxlanTypeDriver, self).__init__(VxlanAllocation) def get_type(self): return p_const.TYPE_VXLAN @@ -83,39 +87,27 @@ class VxlanTypeDriver(type_tunnel.TunnelTypeDriver): self._sync_vxlan_allocations() def reserve_provider_segment(self, session, segment): - segmentation_id = segment.get(api.SEGMENTATION_ID) - with session.begin(subtransactions=True): - try: - alloc = (session.query(VxlanAllocation). - filter_by(vxlan_vni=segmentation_id). - with_lockmode('update'). - one()) - if alloc.allocated: - raise exc.TunnelIdInUse(tunnel_id=segmentation_id) - LOG.debug(_("Reserving specific vxlan tunnel %s from pool"), - segmentation_id) - alloc.allocated = True - except sa_exc.NoResultFound: - LOG.debug(_("Reserving specific vxlan tunnel %s outside pool"), - segmentation_id) - alloc = VxlanAllocation(vxlan_vni=segmentation_id) - alloc.allocated = True - session.add(alloc) - return segment + if self.is_partial_segment(segment): + alloc = self.allocate_partially_specified_segment(session) + if not alloc: + raise exc.NoNetworkAvailable + else: + segmentation_id = segment.get(api.SEGMENTATION_ID) + alloc = self.allocate_fully_specified_segment( + session, vxlan_vni=segmentation_id) + if not alloc: + raise exc.TunnelIdInUse(tunnel_id=segmentation_id) + return {api.NETWORK_TYPE: p_const.TYPE_VXLAN, + api.PHYSICAL_NETWORK: None, + api.SEGMENTATION_ID: alloc.vxlan_vni} def allocate_tenant_segment(self, session): - with session.begin(subtransactions=True): - alloc = (session.query(VxlanAllocation). - filter_by(allocated=False). - with_lockmode('update'). - first()) - if alloc: - LOG.debug(_("Allocating vxlan tunnel vni %(vxlan_vni)s"), - {'vxlan_vni': alloc.vxlan_vni}) - alloc.allocated = True - return {api.NETWORK_TYPE: p_const.TYPE_VXLAN, - api.PHYSICAL_NETWORK: None, - api.SEGMENTATION_ID: alloc.vxlan_vni} + alloc = self.allocate_partially_specified_segment(session) + if not alloc: + return + return {api.NETWORK_TYPE: p_const.TYPE_VXLAN, + api.PHYSICAL_NETWORK: None, + api.SEGMENTATION_ID: alloc.vxlan_vni} def release_segment(self, session, segment): vxlan_vni = segment[api.SEGMENTATION_ID] diff --git a/neutron/tests/unit/ml2/test_type_gre.py b/neutron/tests/unit/ml2/test_type_gre.py index b27fbdbbf..110786f11 100644 --- a/neutron/tests/unit/ml2/test_type_gre.py +++ b/neutron/tests/unit/ml2/test_type_gre.py @@ -51,8 +51,10 @@ class GreTypeTest(base.BaseTestCase): self.driver.validate_provider_segment(segment) segment[api.PHYSICAL_NETWORK] = None - with testtools.ExpectedException(exc.InvalidInput): - self.driver.validate_provider_segment(segment) + self.driver.validate_provider_segment(segment) + + segment[api.SEGMENTATION_ID] = 1 + self.driver.validate_provider_segment(segment) def test_sync_tunnel_allocations(self): self.assertIsNone( @@ -108,9 +110,21 @@ class GreTypeTest(base.BaseTestCase): (TUN_MAX + 5 + 1)) ) - def test_reserve_provider_segment(self): + def test_partial_segment_is_partial_segment(self): segment = {api.NETWORK_TYPE: 'gre', - api.PHYSICAL_NETWORK: 'None', + api.PHYSICAL_NETWORK: None, + api.SEGMENTATION_ID: None} + self.assertTrue(self.driver.is_partial_segment(segment)) + + def test_specific_segment_is_not_partial_segment(self): + segment = {api.NETWORK_TYPE: 'gre', + api.PHYSICAL_NETWORK: None, + api.SEGMENTATION_ID: 101} + self.assertFalse(self.driver.is_partial_segment(segment)) + + def test_reserve_provider_segment_full_specs(self): + segment = {api.NETWORK_TYPE: 'gre', + api.PHYSICAL_NETWORK: None, api.SEGMENTATION_ID: 101} observed = self.driver.reserve_provider_segment(self.session, segment) alloc = self.driver.get_gre_allocation(self.session, @@ -136,6 +150,41 @@ class GreTypeTest(base.BaseTestCase): observed[api.SEGMENTATION_ID]) self.assertIsNone(alloc) + def test_reserve_provider_segment(self): + tunnel_ids = set() + specs = {api.NETWORK_TYPE: 'gre', + api.PHYSICAL_NETWORK: 'None', + api.SEGMENTATION_ID: None} + + for x in xrange(TUN_MIN, TUN_MAX + 1): + segment = self.driver.reserve_provider_segment(self.session, + specs) + self.assertEqual('gre', segment[api.NETWORK_TYPE]) + self.assertThat(segment[api.SEGMENTATION_ID], + matchers.GreaterThan(TUN_MIN - 1)) + self.assertThat(segment[api.SEGMENTATION_ID], + matchers.LessThan(TUN_MAX + 1)) + tunnel_ids.add(segment[api.SEGMENTATION_ID]) + + with testtools.ExpectedException(exc.NoNetworkAvailable): + segment = self.driver.reserve_provider_segment(self.session, + specs) + + segment = {api.NETWORK_TYPE: 'gre', + api.PHYSICAL_NETWORK: 'None', + api.SEGMENTATION_ID: tunnel_ids.pop()} + self.driver.release_segment(self.session, segment) + segment = self.driver.reserve_provider_segment(self.session, specs) + self.assertThat(segment[api.SEGMENTATION_ID], + matchers.GreaterThan(TUN_MIN - 1)) + self.assertThat(segment[api.SEGMENTATION_ID], + matchers.LessThan(TUN_MAX + 1)) + tunnel_ids.add(segment[api.SEGMENTATION_ID]) + + for tunnel_id in tunnel_ids: + segment[api.SEGMENTATION_ID] = tunnel_id + self.driver.release_segment(self.session, segment) + def test_allocate_tenant_segment(self): tunnel_ids = set() for x in moves.xrange(TUN_MIN, TUN_MAX + 1): diff --git a/neutron/tests/unit/ml2/test_type_vxlan.py b/neutron/tests/unit/ml2/test_type_vxlan.py index 836feef17..4512832de 100644 --- a/neutron/tests/unit/ml2/test_type_vxlan.py +++ b/neutron/tests/unit/ml2/test_type_vxlan.py @@ -65,8 +65,10 @@ class VxlanTypeTest(base.BaseTestCase): self.driver.validate_provider_segment(segment) segment[api.PHYSICAL_NETWORK] = None - with testtools.ExpectedException(exc.InvalidInput): - self.driver.validate_provider_segment(segment) + self.driver.validate_provider_segment(segment) + + segment[api.SEGMENTATION_ID] = 1 + self.driver.validate_provider_segment(segment) def test_sync_tunnel_allocations(self): self.assertIsNone( @@ -116,9 +118,21 @@ class VxlanTypeTest(base.BaseTestCase): get_vxlan_allocation(self.session, (TUN_MAX + 5 + 1))) - def test_reserve_provider_segment(self): + def test_partial_segment_is_partial_segment(self): segment = {api.NETWORK_TYPE: 'vxlan', - api.PHYSICAL_NETWORK: 'None', + api.PHYSICAL_NETWORK: None, + api.SEGMENTATION_ID: None} + self.assertTrue(self.driver.is_partial_segment(segment)) + + def test_specific_segment_is_not_partial_segment(self): + segment = {api.NETWORK_TYPE: 'vxlan', + api.PHYSICAL_NETWORK: None, + api.SEGMENTATION_ID: 101} + self.assertFalse(self.driver.is_partial_segment(segment)) + + def test_reserve_provider_segment_full_specs(self): + segment = {api.NETWORK_TYPE: 'vxlan', + api.PHYSICAL_NETWORK: None, api.SEGMENTATION_ID: 101} observed = self.driver.reserve_provider_segment(self.session, segment) alloc = self.driver.get_vxlan_allocation(self.session, @@ -144,6 +158,41 @@ class VxlanTypeTest(base.BaseTestCase): observed[api.SEGMENTATION_ID]) self.assertIsNone(alloc) + def test_reserve_provider_segment(self): + tunnel_ids = set() + specs = {api.NETWORK_TYPE: 'vxlan', + api.PHYSICAL_NETWORK: 'None', + api.SEGMENTATION_ID: None} + + for x in xrange(TUN_MIN, TUN_MAX + 1): + segment = self.driver.reserve_provider_segment(self.session, + specs) + self.assertEqual('vxlan', segment[api.NETWORK_TYPE]) + self.assertThat(segment[api.SEGMENTATION_ID], + matchers.GreaterThan(TUN_MIN - 1)) + self.assertThat(segment[api.SEGMENTATION_ID], + matchers.LessThan(TUN_MAX + 1)) + tunnel_ids.add(segment[api.SEGMENTATION_ID]) + + with testtools.ExpectedException(exc.NoNetworkAvailable): + segment = self.driver.reserve_provider_segment(self.session, + specs) + + segment = {api.NETWORK_TYPE: 'vxlan', + api.PHYSICAL_NETWORK: 'None', + api.SEGMENTATION_ID: tunnel_ids.pop()} + self.driver.release_segment(self.session, segment) + segment = self.driver.reserve_provider_segment(self.session, specs) + self.assertThat(segment[api.SEGMENTATION_ID], + matchers.GreaterThan(TUN_MIN - 1)) + self.assertThat(segment[api.SEGMENTATION_ID], + matchers.LessThan(TUN_MAX + 1)) + tunnel_ids.add(segment[api.SEGMENTATION_ID]) + + for tunnel_id in tunnel_ids: + segment[api.SEGMENTATION_ID] = tunnel_id + self.driver.release_segment(self.session, segment) + def test_allocate_tenant_segment(self): tunnel_ids = set() for x in moves.xrange(TUN_MIN, TUN_MAX + 1): -- 2.45.2