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💯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
This commit is contained in:
Gary Kotton 2017-07-03 02:20:26 -07:00 committed by Kobi Samoray
parent 66935845a3
commit 8a3364c49f
4 changed files with 115 additions and 8 deletions

View File

@ -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 <DVS MoRef ID>:<vlan_min>:<vlan_max> "
"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.

View File

@ -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())

View File

@ -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)

View File

@ -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: