From 926d8a939ab97d0eb41b04d495f865a64da11b43 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Fri, 20 Jun 2014 11:31:01 +0200 Subject: [PATCH] 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 --- neutron/extensions/multiprovidernet.py | 26 +++++++++++++++-------- neutron/plugins/ml2/driver_api.py | 8 +++++++ neutron/plugins/ml2/drivers/type_flat.py | 3 +++ neutron/plugins/ml2/drivers/type_local.py | 3 +++ neutron/plugins/ml2/managers.py | 9 ++++++++ neutron/plugins/ml2/plugin.py | 14 ++++++------ neutron/plugins/vmware/plugins/base.py | 6 ++++-- neutron/tests/unit/ml2/test_ml2_plugin.py | 14 +++++++++++- neutron/tests/unit/ml2/test_type_flat.py | 5 +++++ neutron/tests/unit/ml2/test_type_local.py | 4 ++++ 10 files changed, 73 insertions(+), 19 deletions(-) diff --git a/neutron/extensions/multiprovidernet.py b/neutron/extensions/multiprovidernet.py index 79fcb9e4a06..5df48f63594 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 88ddd017e37..8b0484d13f2 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 ec9c4edbfdc..73c85b39520 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 df22302f96b..ec6a3e14696 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 1b313c7816a..33293823c79 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 0229378b296..a7ae455b7fb 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 d437828083e..6b73707dc88 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 929d5dbd2da..88d27e4c80c 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 35c3a46121c..b752d9ed13a 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 f36b853514b..441886ced0f 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)