From d5b09d891ab52c2eb1dc21bab4faa45e2b18ad81 Mon Sep 17 00:00:00 2001 From: Kyle Mestery Date: Fri, 23 Aug 2013 09:03:49 +0000 Subject: [PATCH] Add support for the multiprovider API to ML2 This implements support for creating provider networks with multiple different segments with the ML2 plugin. Implements: blueprint ml2-multi-segment-api Change-Id: Id6fc22841fddabfbe685de9605d4a4682ee1102d --- neutron/plugins/ml2/plugin.py | 78 ++++++++++----- neutron/tests/unit/ml2/test_ml2_plugin.py | 110 +++++++++++++++++++++- 2 files changed, 162 insertions(+), 26 deletions(-) diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 892685ef5..9d9b01f67 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -29,6 +29,7 @@ from neutron.db import l3_gwmode_db from neutron.db import models_v2 from neutron.db import quota_db # noqa from neutron.db import securitygroups_rpc_base as sg_db_rpc +from neutron.extensions import multiprovidernet as mpnet from neutron.extensions import portbindings from neutron.extensions import providernet as provider from neutron.openstack.common import excutils @@ -77,7 +78,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, _supported_extension_aliases = ["provider", "router", "extraroute", "binding", "quotas", "security-group", "agent", "l3_agent_scheduler", - "dhcp_agent_scheduler", "ext-gw-mode"] + "dhcp_agent_scheduler", "ext-gw-mode", + "multi-provider"] @property def supported_extension_aliases(self): @@ -123,25 +125,48 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, fanout=False) self.conn.consume_in_thread() - def _process_provider_create(self, context, attrs): - network_type = self._get_attribute(attrs, provider.NETWORK_TYPE) - physical_network = self._get_attribute(attrs, + def _process_provider_segment(self, segment): + network_type = self._get_attribute(segment, provider.NETWORK_TYPE) + physical_network = self._get_attribute(segment, provider.PHYSICAL_NETWORK) - segmentation_id = self._get_attribute(attrs, provider.SEGMENTATION_ID) + segmentation_id = self._get_attribute(segment, + provider.SEGMENTATION_ID) if attributes.is_attr_set(network_type): segment = {api.NETWORK_TYPE: network_type, api.PHYSICAL_NETWORK: physical_network, api.SEGMENTATION_ID: segmentation_id} self.type_manager.validate_provider_segment(segment) - return segment - if (attributes.is_attr_set(attrs.get(provider.PHYSICAL_NETWORK)) or - attributes.is_attr_set(attrs.get(provider.SEGMENTATION_ID))): - msg = _("network_type required if other provider attributes " - "specified") - raise exc.InvalidInput(error_message=msg) + msg = _("network_type required") + 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)): + # Verify that multiprovider and provider attributes are not set + # at the same time. + if attributes.is_attr_set(network.get(mpnet.SEGMENTS)): + raise mpnet.SegmentsSetInConjunctionWithProviders() + + network_type = self._get_attribute(network, provider.NETWORK_TYPE) + physical_network = self._get_attribute(network, + provider.PHYSICAL_NETWORK) + segmentation_id = self._get_attribute(network, + provider.SEGMENTATION_ID) + segments = [{provider.NETWORK_TYPE: network_type, + provider.PHYSICAL_NETWORK: physical_network, + provider.SEGMENTATION_ID: segmentation_id}] + 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] def _get_attribute(self, attrs, key): value = attrs.get(key) @@ -158,9 +183,11 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, network[provider.PHYSICAL_NETWORK] = None network[provider.SEGMENTATION_ID] = None elif len(segments) > 1: - network[provider.NETWORK_TYPE] = TYPE_MULTI_SEGMENT - network[provider.PHYSICAL_NETWORK] = None - network[provider.SEGMENTATION_ID] = None + network[mpnet.SEGMENTS] = [ + {provider.NETWORK_TYPE: segment[api.NETWORK_TYPE], + provider.PHYSICAL_NETWORK: segment[api.PHYSICAL_NETWORK], + provider.SEGMENTATION_ID: segment[api.SEGMENTATION_ID]} + for segment in segments] else: segment = segments[0] network[provider.NETWORK_TYPE] = segment[api.NETWORK_TYPE] @@ -260,23 +287,26 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, # TODO(apech): Need to override bulk operations def create_network(self, context, network): - attrs = network['network'] - segment = self._process_provider_create(context, attrs) - tenant_id = self._get_tenant_id_for_create(context, attrs) + net_data = network['network'] + segments = self._process_provider_create(net_data) + tenant_id = self._get_tenant_id_for_create(context, net_data) session = context.session with session.begin(subtransactions=True): self._ensure_default_security_group(context, tenant_id) - if segment: - self.type_manager.reserve_provider_segment(session, segment) - else: - segment = self.type_manager.allocate_tenant_segment(session) result = super(Ml2Plugin, self).create_network(context, network) - id = result['id'] - self._process_l3_create(context, result, attrs) + network_id = result['id'] + self._process_l3_create(context, result, net_data) # REVISIT(rkukura): Consider moving all segment management # to TypeManager. - db.add_network_segment(session, id, segment) + if segments: + for segment in segments: + self.type_manager.reserve_provider_segment(session, + segment) + db.add_network_segment(session, network_id, segment) + else: + segment = self.type_manager.allocate_tenant_segment(session) + db.add_network_segment(session, network_id, segment) self._extend_network_dict_provider(context, result) mech_context = driver_context.NetworkContext(self, context, result) diff --git a/neutron/tests/unit/ml2/test_ml2_plugin.py b/neutron/tests/unit/ml2/test_ml2_plugin.py index 202230d41..e93bcb328 100644 --- a/neutron/tests/unit/ml2/test_ml2_plugin.py +++ b/neutron/tests/unit/ml2/test_ml2_plugin.py @@ -13,8 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron.extensions import multiprovidernet as mpnet from neutron.extensions import portbindings -from neutron.plugins.ml2 import config as config +from neutron.extensions import providernet as pnet +from neutron.plugins.ml2 import config from neutron.tests.unit import _test_extension_portbindings as test_bindings from neutron.tests.unit import test_db_plugin as test_plugin from neutron.tests.unit import test_extension_ext_gw_mode @@ -34,7 +36,12 @@ class Ml2PluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase): # driver apis. config.cfg.CONF.set_override('mechanism_drivers', ['logger', 'test'], - 'ml2') + group='ml2') + self.physnet = 'physnet1' + self.vlan_range = '1:100' + self.phys_vrange = ':'.join([self.physnet, self.vlan_range]) + config.cfg.CONF.set_override('network_vlan_ranges', [self.phys_vrange], + group='ml2_type_vlan') self.addCleanup(config.cfg.CONF.reset) super(Ml2PluginV2TestCase, self).setUp(PLUGIN_NAME) self.port_create_status = 'DOWN' @@ -89,3 +96,102 @@ class TestMl2PortBindingHost(Ml2PluginV2TestCase, class TestMl2ExtGwModeSupport(Ml2PluginV2TestCase, test_extension_ext_gw_mode.ExtGwModeTestCase): pass + + +class TestMultiSegmentNetworks(Ml2PluginV2TestCase): + + def setUp(self, plugin=None): + super(TestMultiSegmentNetworks, self).setUp() + + def test_create_network_provider(self): + data = {'network': {'name': 'net1', + pnet.NETWORK_TYPE: 'vlan', + pnet.PHYSICAL_NETWORK: 'physnet1', + pnet.SEGMENTATION_ID: 1, + 'tenant_id': 'tenant_one'}} + network_req = self.new_create_request('networks', data) + network = self.deserialize(self.fmt, + network_req.get_response(self.api)) + self.assertEqual(network['network'][pnet.NETWORK_TYPE], 'vlan') + self.assertEqual(network['network'][pnet.PHYSICAL_NETWORK], 'physnet1') + self.assertEqual(network['network'][pnet.SEGMENTATION_ID], 1) + self.assertNotIn(mpnet.SEGMENTS, network['network']) + + def test_create_network_single_multiprovider(self): + data = {'network': {'name': 'net1', + mpnet.SEGMENTS: + [{pnet.NETWORK_TYPE: 'vlan', + pnet.PHYSICAL_NETWORK: 'physnet1', + pnet.SEGMENTATION_ID: 1}], + 'tenant_id': 'tenant_one'}} + net_req = self.new_create_request('networks', data) + network = self.deserialize(self.fmt, net_req.get_response(self.api)) + self.assertEqual(network['network'][pnet.NETWORK_TYPE], 'vlan') + self.assertEqual(network['network'][pnet.PHYSICAL_NETWORK], 'physnet1') + self.assertEqual(network['network'][pnet.SEGMENTATION_ID], 1) + self.assertNotIn(mpnet.SEGMENTS, network['network']) + + # Tests get_network() + net_req = self.new_show_request('networks', network['network']['id']) + network = self.deserialize(self.fmt, net_req.get_response(self.api)) + self.assertEqual(network['network'][pnet.NETWORK_TYPE], 'vlan') + self.assertEqual(network['network'][pnet.PHYSICAL_NETWORK], 'physnet1') + self.assertEqual(network['network'][pnet.SEGMENTATION_ID], 1) + self.assertNotIn(mpnet.SEGMENTS, network['network']) + + def test_create_network_multiprovider(self): + data = {'network': {'name': 'net1', + mpnet.SEGMENTS: + [{pnet.NETWORK_TYPE: 'vlan', + pnet.PHYSICAL_NETWORK: 'physnet1', + pnet.SEGMENTATION_ID: 1}, + {pnet.NETWORK_TYPE: 'vlan', + pnet.PHYSICAL_NETWORK: 'physnet1', + pnet.SEGMENTATION_ID: 2}], + 'tenant_id': 'tenant_one'}} + network_req = self.new_create_request('networks', data) + network = self.deserialize(self.fmt, + network_req.get_response(self.api)) + tz = network['network'][mpnet.SEGMENTS] + for tz in data['network'][mpnet.SEGMENTS]: + for field in [pnet.NETWORK_TYPE, pnet.PHYSICAL_NETWORK, + pnet.SEGMENTATION_ID]: + self.assertEqual(tz.get(field), tz.get(field)) + + # Tests get_network() + net_req = self.new_show_request('networks', network['network']['id']) + network = self.deserialize(self.fmt, net_req.get_response(self.api)) + tz = network['network'][mpnet.SEGMENTS] + for tz in data['network'][mpnet.SEGMENTS]: + for field in [pnet.NETWORK_TYPE, pnet.PHYSICAL_NETWORK, + pnet.SEGMENTATION_ID]: + self.assertEqual(tz.get(field), tz.get(field)) + + def test_create_network_with_provider_and_multiprovider_fail(self): + data = {'network': {'name': 'net1', + mpnet.SEGMENTS: + [{pnet.NETWORK_TYPE: 'vlan', + pnet.PHYSICAL_NETWORK: 'physnet1', + pnet.SEGMENTATION_ID: 1}], + pnet.NETWORK_TYPE: 'vlan', + pnet.PHYSICAL_NETWORK: 'physnet1', + pnet.SEGMENTATION_ID: 1, + 'tenant_id': 'tenant_one'}} + + network_req = self.new_create_request('networks', data) + res = network_req.get_response(self.api) + self.assertEqual(res.status_int, 400) + + def test_create_network_duplicate_segments(self): + data = {'network': {'name': 'net1', + mpnet.SEGMENTS: + [{pnet.NETWORK_TYPE: 'vlan', + pnet.PHYSICAL_NETWORK: 'physnet1', + pnet.SEGMENTATION_ID: 1}, + {pnet.NETWORK_TYPE: 'vlan', + pnet.PHYSICAL_NETWORK: 'physnet1', + pnet.SEGMENTATION_ID: 1}], + 'tenant_id': 'tenant_one'}} + network_req = self.new_create_request('networks', data) + res = network_req.get_response(self.api) + self.assertEqual(res.status_int, 400) -- 2.45.2