]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add partial specs support in ML2 for gre/vxlan provider networks
authorCedric Brandily <zzelle@gmail.com>
Mon, 16 Jun 2014 20:03:14 +0000 (22:03 +0200)
committerCedric Brandily <zzelle@gmail.com>
Mon, 21 Jul 2014 20:00:59 +0000 (20:00 +0000)
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
neutron/plugins/ml2/drivers/type_tunnel.py
neutron/plugins/ml2/drivers/type_vxlan.py
neutron/tests/unit/ml2/test_type_gre.py
neutron/tests/unit/ml2/test_type_vxlan.py

index 768d1fc355e0f85993c89eb40263af2443ea5186..9ade7db7bbb2fb807406fa9aee8f581621f63f35 100644 (file)
@@ -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 "<GreTunnelEndpoint(%s)>" % 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]
index fbc7110eae9a22dc48eb69da321c9f2aec3cf774..17d529f5c2b9d77264c5dc023af70e43837f6efd 100644 (file)
@@ -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]:
index d77aa1e707aea79121ef576532a1ca11adc1b1f0..9ddcf735d755ff70a74edbd1ec5affa176e254ef 100644 (file)
@@ -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 "<VxlanTunnelEndpoint(%s)>" % 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]
index b27fbdbbf20b793abb3b9d128ecea0c903f56586..110786f11e42ac5182982f869864add5d939bf23 100644 (file)
@@ -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):
index 836feef174bb364593803e4ee8804ece6e28603f..4512832de299b8b5a8ad0ad5464fb157d7a03be1 100644 (file)
@@ -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):