From 8a3364c49fb10a96d4578eae72ef12357ef0a8c5 Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Mon, 3 Jul 2017 02:20:26 -0700 Subject: [PATCH] NSX|V: enable plugin to decide on VLAN tag Enable an option where the plugin would decide on the VLAN tag for a provider network. This is done as follows: 1. In the configuration file the admin will need to add the supported DVS's and their respective VLAN ranges. For example: network_vlan_ranges = dvs-22,dvs-70:100:102,dvs-70:110:120 This means that dvs-22 can allocate any VLAN tag. On dvs-70 tags can be selected between and including 100 and 102 and 110 and 120. 2. When the admin created the provider network she/he need only state the provider:physical_network (must be one defined above). If they select a VLAN id then the selected one will be used. If not one from the ranges above will be used. Change-Id: Ieeebc790fa5a4e9480308dcd11f495662e4c48c2 --- vmware_nsx/common/config.py | 6 ++ vmware_nsx/db/nsxv_db.py | 12 ++++ vmware_nsx/plugins/nsx_v/plugin.py | 76 +++++++++++++++++++--- vmware_nsx/tests/unit/nsx_v/test_plugin.py | 29 +++++++++ 4 files changed, 115 insertions(+), 8 deletions(-) diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index f65f6b34bc..cda5552d4f 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -675,6 +675,12 @@ nsxv_opts = [ default=2, help=_("(Optional) Set the wait time (Seconds) between " "enablement of ECMP.")), + cfg.ListOpt('network_vlan_ranges', + default=[], + help=_("List of :: " + "specifying DVS MoRef ID usable for VLAN provider " + "networks, as well as ranges of VLAN tags on each " + "available for allocation to networks.")), ] # define the configuration of each NSX-V availability zone. diff --git a/vmware_nsx/db/nsxv_db.py b/vmware_nsx/db/nsxv_db.py index f65ba4d529..48f4d12b3c 100644 --- a/vmware_nsx/db/nsxv_db.py +++ b/vmware_nsx/db/nsxv_db.py @@ -528,6 +528,18 @@ def get_network_bindings_by_vlanid_and_physical_net(session, vlan_id, all()) +def get_network_bindings_by_ids(session, vlan_id, phy_uuid): + return get_network_bindings_by_vlanid_and_physical_net( + session, vlan_id, phy_uuid) + + +def get_network_bindings_by_physical_net(session, phy_uuid): + session = session or db.get_reader_session() + return (session.query(nsxv_models.NsxvTzNetworkBinding). + filter_by(phy_uuid=phy_uuid). + all()) + + def delete_network_bindings(session, network_id): return (session.query(nsxv_models.NsxvTzNetworkBinding). filter_by(network_id=network_id).delete()) diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 64ed760825..1dcf5368c9 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -42,6 +42,7 @@ from oslo_utils import excutils from oslo_utils import netutils from oslo_utils import uuidutils import six +from six import moves from sqlalchemy.orm import exc as sa_exc from neutron.api import extensions as neutron_extensions @@ -223,6 +224,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self._extension_manager.extension_aliases()) self.metadata_proxy_handler = None config.validate_nsxv_config_options() + self._network_vlans = utils.parse_network_vlan_ranges( + cfg.CONF.nsxv.network_vlan_ranges) neutron_extensions.append_api_extensions_path( [vmware_nsx.NSX_EXT_PATH]) @@ -592,6 +595,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, if not validators.is_attr_set(network.get(mpnet.SEGMENTS)): return + az_dvs = self._get_network_az_dvs_id(network) for segment in network[mpnet.SEGMENTS]: network_type = segment.get(pnet.NETWORK_TYPE) physical_network = segment.get(pnet.PHYSICAL_NETWORK) @@ -599,7 +603,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, network_type_set = validators.is_attr_set(network_type) segmentation_id_set = validators.is_attr_set(segmentation_id) physical_network_set = validators.is_attr_set(physical_network) - az_dvs = self._get_network_az_dvs_id(network) err_msg = None if not network_type_set: @@ -611,9 +614,17 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, if physical_network_set: self._validate_physical_network(physical_network, az_dvs) elif network_type == c_utils.NsxVNetworkTypes.VLAN: + # Verify whether the DVSes exist in the backend. + if physical_network_set: + self._validate_physical_network(physical_network, az_dvs) if not segmentation_id_set: - err_msg = _("Segmentation ID must be specified with " - "vlan network type") + if physical_network_set: + if physical_network not in self._network_vlans: + err_msg = _("Invalid physical network for " + "segmentation ID allocation") + else: + err_msg = _("Segmentation ID must be specified with " + "vlan network type") elif (segmentation_id_set and not utils.is_valid_vlan_tag(segmentation_id)): err_msg = (_("%(segmentation_id)s out of range " @@ -629,16 +640,13 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, if physical_network_set: phy_uuid = physical_network else: - # use the fvs_id of the availability zone + # use the dvs_id of the availability zone phy_uuid = az_dvs for binding in bindings: if binding['phy_uuid'] == phy_uuid: raise n_exc.VlanIdInUse( vlan_id=segmentation_id, physical_network=phy_uuid) - # Verify whether the DVSes exist in the backend. - if physical_network_set: - self._validate_physical_network(physical_network, az_dvs) elif network_type == c_utils.NsxVNetworkTypes.VXLAN: # Currently unable to set the segmentation id @@ -1006,6 +1014,25 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # Use the dvs_id of the availability zone return self._get_network_az_dvs_id(net_data) + def _generate_segment_id(self, context, physical_network, net_data): + bindings = nsxv_db.get_network_bindings_by_physical_net( + context.session, physical_network) + vlan_ranges = self._network_vlans.get(physical_network, []) + if vlan_ranges: + vlan_ids = set() + for vlan_min, vlan_max in vlan_ranges: + vlan_ids |= set(moves.range(vlan_min, vlan_max + 1)) + else: + vlan_min = constants.MIN_VLAN_TAG + vlan_max = constants.MAX_VLAN_TAG + vlan_ids = set(moves.range(vlan_min, vlan_max + 1)) + used_ids_in_range = set([binding.vlan_id for binding in bindings + if binding.vlan_id in vlan_ids]) + free_ids = list(vlan_ids ^ used_ids_in_range) + if len(free_ids) == 0: + raise n_exc.NoNetworkAvailable() + net_data[mpnet.SEGMENTS][0][pnet.SEGMENTATION_ID] = free_ids[0] + def create_network(self, context, network): net_data = network['network'] tenant_id = net_data['tenant_id'] @@ -1020,16 +1047,49 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, backend_network = (not validators.is_attr_set(external) or validators.is_attr_set(external) and not external) network_type = None + generate_segmenation_id = False + lock_vlan_creation = False if provider_type is not None: segment = net_data[mpnet.SEGMENTS][0] network_type = segment.get(pnet.NETWORK_TYPE) + if network_type == c_utils.NsxVNetworkTypes.VLAN: + physical_network = segment.get(pnet.PHYSICAL_NETWORK) + if physical_network in self._network_vlans: + lock_vlan_creation = True + if not validators.is_attr_set( + segment.get(pnet.SEGMENTATION_ID)): + generate_segmenation_id = True + if lock_vlan_creation: + with locking.LockManager.get_lock( + 'vlan-networking-%s' % physical_network): + if generate_segmenation_id: + self._generate_segment_id(context, physical_network, + net_data) + else: + segmentation_id = segment.get(pnet.SEGMENTATION_ID) + if nsxv_db.get_network_bindings_by_ids(context.session, + segmentation_id, physical_network): + raise n_exc.VlanIdInUse( + vlan_id=segmentation_id, + physical_network=physical_network) + return self._create_network(context, network, net_data, + provider_type, external, + backend_network, network_type) + else: + return self._create_network(context, network, net_data, + provider_type, external, + backend_network, network_type) + + def _create_network(self, context, network, net_data, + provider_type, external, backend_network, + network_type): # A external network should be created in the case that we have a flat, # vlan or vxlan network. For port groups we do not make any changes. external_backend_network = ( external and provider_type is not None and network_type != c_utils.NsxVNetworkTypes.PORTGROUP) - self._validate_network_qos(net_data, backend_network) # Update the transparent vlan if configured + self._validate_network_qos(net_data, backend_network) vlt = False if n_utils.is_extension_supported(self, 'vlan-transparent'): vlt = ext_vlan.get_vlan_transparent(net_data) diff --git a/vmware_nsx/tests/unit/nsx_v/test_plugin.py b/vmware_nsx/tests/unit/nsx_v/test_plugin.py index 213854d567..c5321d4034 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v/test_plugin.py @@ -29,6 +29,7 @@ from neutron.extensions import l3_ext_gw_mode from neutron.extensions import l3_flavors from neutron.extensions import router_availability_zone from neutron.extensions import securitygroup as secgrp +from neutron.plugins.common import utils from neutron.services.qos import qos_consts from neutron.tests.unit import _test_extension_portbindings as test_bindings import neutron.tests.unit.db.test_allowedaddresspairs_db as test_addr_pair @@ -270,6 +271,34 @@ class TestNetworksV2(test_plugin.TestNetworksV2, NsxVPluginV2TestCase): def test_create_bridge_vlan_network(self): self._test_create_bridge_network(vlan_id=123) + def _test_generate_tag(self, vlan_id): + net_type = 'vlan' + name = 'bridge_net' + plugin = directory.get_plugin() + plugin._network_vlans = utils.parse_network_vlan_ranges( + cfg.CONF.nsxv.network_vlan_ranges) + expected = [('subnets', []), ('name', name), ('admin_state_up', True), + ('status', 'ACTIVE'), ('shared', False), + (pnet.NETWORK_TYPE, net_type), + (pnet.PHYSICAL_NETWORK, 'dvs-70'), + (pnet.SEGMENTATION_ID, vlan_id)] + providernet_args = {pnet.NETWORK_TYPE: net_type, + pnet.PHYSICAL_NETWORK: 'dvs-70'} + with self.network(name=name, + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.PHYSICAL_NETWORK)) as net: + for k, v in expected: + self.assertEqual(net['network'][k], v) + + def test_create_bridge_vlan_generate(self): + cfg.CONF.set_default('network_vlan_ranges', 'dvs-70', 'nsxv') + self._test_generate_tag(1) + + def test_create_bridge_vlan_generate_range(self): + cfg.CONF.set_default('network_vlan_ranges', 'dvs-70:100:110', 'nsxv') + self._test_generate_tag(100) + def test_create_bridge_vlan_network_outofrange_returns_400(self): with testlib_api.ExpectedException( webob.exc.HTTPClientError) as ctx_manager: