From: Cedric Brandily Date: Fri, 20 Jun 2014 09:31:01 +0000 (+0200) Subject: Add partial specs support in ML2 for multiprovider extension X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=926d8a939ab97d0eb41b04d495f865a64da11b43;p=openstack-build%2Fneutron-build.git Add partial specs support in ML2 for multiprovider extension 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 --- diff --git a/neutron/extensions/multiprovidernet.py b/neutron/extensions/multiprovidernet.py index 79fcb9e4a..5df48f635 100644 --- a/neutron/extensions/multiprovidernet.py +++ b/neutron/extensions/multiprovidernet.py @@ -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() diff --git a/neutron/plugins/ml2/driver_api.py b/neutron/plugins/ml2/driver_api.py index 88ddd017e..8b0484d13 100644 --- a/neutron/plugins/ml2/driver_api.py +++ b/neutron/plugins/ml2/driver_api.py @@ -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. diff --git a/neutron/plugins/ml2/drivers/type_flat.py b/neutron/plugins/ml2/drivers/type_flat.py index ec9c4edbf..73c85b395 100644 --- a/neutron/plugins/ml2/drivers/type_flat.py +++ b/neutron/plugins/ml2/drivers/type_flat.py @@ -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: diff --git a/neutron/plugins/ml2/drivers/type_local.py b/neutron/plugins/ml2/drivers/type_local.py index df22302f9..ec6a3e146 100644 --- a/neutron/plugins/ml2/drivers/type_local.py +++ b/neutron/plugins/ml2/drivers/type_local.py @@ -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]: diff --git a/neutron/plugins/ml2/managers.py b/neutron/plugins/ml2/managers.py index 1b313c781..33293823c 100644 --- a/neutron/plugins/ml2/managers.py +++ b/neutron/plugins/ml2/managers.py @@ -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) diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 0229378b2..a7ae455b7 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -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) diff --git a/neutron/plugins/vmware/plugins/base.py b/neutron/plugins/vmware/plugins/base.py index d43782808..6b73707dc 100644 --- a/neutron/plugins/vmware/plugins/base.py +++ b/neutron/plugins/vmware/plugins/base.py @@ -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) diff --git a/neutron/tests/unit/ml2/test_ml2_plugin.py b/neutron/tests/unit/ml2/test_ml2_plugin.py index 929d5dbd2..88d27e4c8 100644 --- a/neutron/tests/unit/ml2/test_ml2_plugin.py +++ b/neutron/tests/unit/ml2/test_ml2_plugin.py @@ -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', diff --git a/neutron/tests/unit/ml2/test_type_flat.py b/neutron/tests/unit/ml2/test_type_flat.py index 35c3a4612..b752d9ed1 100644 --- a/neutron/tests/unit/ml2/test_type_flat.py +++ b/neutron/tests/unit/ml2/test_type_flat.py @@ -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'} diff --git a/neutron/tests/unit/ml2/test_type_local.py b/neutron/tests/unit/ml2/test_type_local.py index f36b85351..441886ced 100644 --- a/neutron/tests/unit/ml2/test_type_local.py +++ b/neutron/tests/unit/ml2/test_type_local.py @@ -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)