]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add partial specs support in ML2 for multiprovider extension
authorCedric Brandily <zzelle@gmail.com>
Fri, 20 Jun 2014 09:31:01 +0000 (11:31 +0200)
committerCedric Brandily <zzelle@gmail.com>
Mon, 21 Jul 2014 20:02:14 +0000 (20:02 +0000)
ML2 provider network partial specs let admins choose some
multiprovider network attributes and let neutron choose remaining
attributes. This change provides the implementation for
multiprovider networks.

In practice, for VLAN/GRE/VXLAN segments provider:segmentation_id
choice can be delegated to neutron, in such case neutron try to find
a segment in tenant network pools which respects provided segment
attributes. For VLAN segments provider:physical_network choice can
also be delegated.

DocImpact

Implements blueprint provider-network-partial-specs

Change-Id: I1cf1441a179ec527674276b71e9924841f8570b6

neutron/extensions/multiprovidernet.py
neutron/plugins/ml2/driver_api.py
neutron/plugins/ml2/drivers/type_flat.py
neutron/plugins/ml2/drivers/type_local.py
neutron/plugins/ml2/managers.py
neutron/plugins/ml2/plugin.py
neutron/plugins/vmware/plugins/base.py
neutron/tests/unit/ml2/test_ml2_plugin.py
neutron/tests/unit/ml2/test_type_flat.py
neutron/tests/unit/ml2/test_type_local.py

index 79fcb9e4a0694ebcd430d60bb87d08dfb2bf47bc..5df48f63594183d948b00acc2730cda077b6f9b5 100644 (file)
@@ -32,15 +32,9 @@ class SegmentsContainDuplicateEntry(qexception.InvalidInput):
 
 
 def _convert_and_validate_segments(segments, valid_values=None):
-    unique = set()
     for segment in segments:
-        unique.add(tuple(segment.iteritems()))
-        network_type = segment.get(pnet.NETWORK_TYPE,
-                                   attr.ATTR_NOT_SPECIFIED)
-        segment[pnet.NETWORK_TYPE] = network_type
-        physical_network = segment.get(pnet.PHYSICAL_NETWORK,
-                                       attr.ATTR_NOT_SPECIFIED)
-        segment[pnet.PHYSICAL_NETWORK] = physical_network
+        segment.setdefault(pnet.NETWORK_TYPE, attr.ATTR_NOT_SPECIFIED)
+        segment.setdefault(pnet.PHYSICAL_NETWORK, attr.ATTR_NOT_SPECIFIED)
         segmentation_id = segment.get(pnet.SEGMENTATION_ID)
         if segmentation_id:
             segment[pnet.SEGMENTATION_ID] = attr.convert_to_int(
@@ -53,7 +47,21 @@ def _convert_and_validate_segments(segments, valid_values=None):
                              set([pnet.NETWORK_TYPE, pnet.PHYSICAL_NETWORK,
                                   pnet.SEGMENTATION_ID])))
             raise webob.exc.HTTPBadRequest(msg)
-    if len(unique) != len(segments):
+
+
+def check_duplicate_segments(segments, is_partial_func=None):
+    """Helper function checking duplicate segments.
+
+    If is_partial_funcs is specified and not None, then
+    SegmentsContainDuplicateEntry is raised if two segments are identical and
+    non partially defined (is_partial_func(segment) == False).
+    Otherwise SegmentsContainDuplicateEntry is raised if two segment are
+    identical.
+    """
+    if is_partial_func is not None:
+        segments = [s for s in segments if not is_partial_func(s)]
+    fully_specifieds = [tuple(sorted(s.items())) for s in segments]
+    if len(set(fully_specifieds)) != len(fully_specifieds):
         raise SegmentsContainDuplicateEntry()
 
 
index 88ddd017e370239ebaf423411927fd13cf171529..8b0484d13f26b12a3bc0c201353bc77dbd52c34e 100644 (file)
@@ -63,6 +63,14 @@ class TypeDriver(object):
         """
         pass
 
+    @abc.abstractmethod
+    def is_partial_segment(self, segment):
+        """Return True if segment is a partially specified segment.
+
+        :param segment: segment dictionary
+        :returns: boolean
+        """
+
     @abc.abstractmethod
     def validate_provider_segment(self, segment):
         """Validate attributes of a provider network segment.
index ec9c4edbfdc13f0f4ef8e60ac799b9a4445c494d..73c85b39520ece5262f153bccbc654a412151618 100644 (file)
@@ -81,6 +81,9 @@ class FlatTypeDriver(api.TypeDriver):
     def initialize(self):
         LOG.info(_("ML2 FlatTypeDriver initialization complete"))
 
+    def is_partial_segment(self, segment):
+        return False
+
     def validate_provider_segment(self, segment):
         physical_network = segment.get(api.PHYSICAL_NETWORK)
         if not physical_network:
index df22302f96b13c36239572b2de47ab0e21cc84e5..ec6a3e146960864c0734e5f2dc8d4f63e925c75d 100644 (file)
@@ -40,6 +40,9 @@ class LocalTypeDriver(api.TypeDriver):
     def initialize(self):
         pass
 
+    def is_partial_segment(self, segment):
+        return False
+
     def validate_provider_segment(self, segment):
         for key, value in segment.iteritems():
             if value and key not in [api.NETWORK_TYPE]:
index 1b313c7816a1de9cb70b2a56a0a9c555e5398a3d..33293823c7944fd1deb860e1faa8e092cdb92253 100644 (file)
@@ -73,6 +73,15 @@ class TypeManager(stevedore.named.NamedExtensionManager):
             LOG.info(_("Initializing driver for type '%s'"), network_type)
             driver.obj.initialize()
 
+    def is_partial_segment(self, segment):
+        network_type = segment[api.NETWORK_TYPE]
+        driver = self.drivers.get(network_type)
+        if driver:
+            return driver.obj.is_partial_segment(segment)
+        else:
+            msg = _("network_type value '%s' not supported") % network_type
+            raise exc.InvalidInput(error_message=msg)
+
     def validate_provider_segment(self, segment):
         network_type = segment[api.NETWORK_TYPE]
         driver = self.drivers.get(network_type)
index 0229378b29620b558990437d23c3891dd8c4e6d3..a7ae455b7fb8d3f1ce7ca2b060efcc8093f821d5 100644 (file)
@@ -157,8 +157,6 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
         raise exc.InvalidInput(error_message=msg)
 
     def _process_provider_create(self, network):
-        segments = []
-
         if any(attributes.is_attr_set(network.get(f))
                for f in (provider.NETWORK_TYPE, provider.PHYSICAL_NETWORK,
                          provider.SEGMENTATION_ID)):
@@ -175,12 +173,14 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
             segments = [{provider.NETWORK_TYPE: network_type,
                          provider.PHYSICAL_NETWORK: physical_network,
                          provider.SEGMENTATION_ID: segmentation_id}]
+            return [self._process_provider_segment(s) for s in segments]
         elif attributes.is_attr_set(network.get(mpnet.SEGMENTS)):
-            segments = network[mpnet.SEGMENTS]
-        else:
-            return
-
-        return [self._process_provider_segment(s) for s in segments]
+            segments = [self._process_provider_segment(s)
+                        for s in network[mpnet.SEGMENTS]]
+            mpnet.check_duplicate_segments(
+                segments,
+                self.type_manager.is_partial_segment)
+            return segments
 
     def _get_attribute(self, attrs, key):
         value = attrs.get(key)
index d437828083e849fc667178faf079794a52cd252c..6b73707dc8870d6e063e496ef39d4157fc504e44 100644 (file)
@@ -745,10 +745,12 @@ class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                                webob.exc.HTTPBadRequest})
 
     def _validate_provider_create(self, context, network):
-        if not attr.is_attr_set(network.get(mpnet.SEGMENTS)):
+        segments = network.get(mpnet.SEGMENTS)
+        if not attr.is_attr_set(segments):
             return
 
-        for segment in network[mpnet.SEGMENTS]:
+        mpnet.check_duplicate_segments(segments)
+        for segment in segments:
             network_type = segment.get(pnet.NETWORK_TYPE)
             physical_network = segment.get(pnet.PHYSICAL_NETWORK)
             segmentation_id = segment.get(pnet.SEGMENTATION_ID)
index 929d5dbd2da129cb9bceb59d2cb7ca24665864c4..88d27e4c80c29523d8f464273a5e9c9280a1390d 100644 (file)
@@ -326,7 +326,7 @@ class TestMultiSegmentNetworks(Ml2PluginV2TestCase):
         res = network_req.get_response(self.api)
         self.assertEqual(400, res.status_int)
 
-    def test_create_network_duplicate_segments(self):
+    def test_create_network_duplicate_full_segments(self):
         data = {'network': {'name': 'net1',
                             mpnet.SEGMENTS:
                             [{pnet.NETWORK_TYPE: 'vlan',
@@ -340,6 +340,18 @@ class TestMultiSegmentNetworks(Ml2PluginV2TestCase):
         res = network_req.get_response(self.api)
         self.assertEqual(400, res.status_int)
 
+    def test_create_network_duplicate_partial_segments(self):
+        data = {'network': {'name': 'net1',
+                            mpnet.SEGMENTS:
+                            [{pnet.NETWORK_TYPE: 'vlan',
+                              pnet.PHYSICAL_NETWORK: 'physnet1'},
+                             {pnet.NETWORK_TYPE: 'vlan',
+                              pnet.PHYSICAL_NETWORK: 'physnet1'}],
+                            'tenant_id': 'tenant_one'}}
+        network_req = self.new_create_request('networks', data)
+        res = network_req.get_response(self.api)
+        self.assertEqual(201, res.status_int)
+
     def test_release_segment_no_type_driver(self):
         segment = {driver_api.NETWORK_TYPE: 'faketype',
                    driver_api.PHYSICAL_NETWORK: 'physnet1',
index 35c3a46121c79d81fa4da20aa78e83ed8229ccd0..b752d9ed13a47da736cc709f3b556e5f693f2a64 100644 (file)
@@ -40,6 +40,11 @@ class FlatTypeTest(base.BaseTestCase):
         return session.query(type_flat.FlatAllocation).filter_by(
             physical_network=segment[api.PHYSICAL_NETWORK]).first()
 
+    def test_is_partial_segment(self):
+        segment = {api.NETWORK_TYPE: p_const.TYPE_FLAT,
+                   api.PHYSICAL_NETWORK: 'flat_net1'}
+        self.assertFalse(self.driver.is_partial_segment(segment))
+
     def test_validate_provider_segment(self):
         segment = {api.NETWORK_TYPE: p_const.TYPE_FLAT,
                    api.PHYSICAL_NETWORK: 'flat_net1'}
index f36b853514bf7d5715d96414ef32383681a4f4d0..441886ced0f7094ce0d1e0345f12a86ea74301d2 100644 (file)
@@ -27,6 +27,10 @@ class LocalTypeTest(base.BaseTestCase):
         self.driver = type_local.LocalTypeDriver()
         self.session = None
 
+    def test_is_partial_segment(self):
+        segment = {api.NETWORK_TYPE: p_const.TYPE_LOCAL}
+        self.assertFalse(self.driver.is_partial_segment(segment))
+
     def test_validate_provider_segment(self):
         segment = {api.NETWORK_TYPE: p_const.TYPE_LOCAL}
         self.driver.validate_provider_segment(segment)