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 98485da5bfd..a8ba4b83e44 100644 --- a/neutron/plugins/vmware/plugins/base.py +++ b/neutron/plugins/vmware/plugins/base.py @@ -746,10 +746,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)