Enable configuration to decide on vlan tag per TZ

NSX|V3: This feature will enable an admin user to configure a range
of VLAN IDs per VLAN Transport Zone, so when they create a VLAN,
the VLAN tag will be set accordingly.
The configuration is being done in the nsx.ini file, under the relevant
section for nsx-v3, the admin will note the tz-id, with either a
predefined range(s) (min/max values) or only the transport zone itself
(which means that any value can be chosen).
The admin user will create the network noting “provider:physical_network”,
if they select a VLAN ID, than it will be used, if not - one will be
chosen according to the configuration mentioned above.
New configuration variable in nsx.ini under nsx_v3: network_vlan_ranges
network_vlan_ranges=<TZ_UUID>:<min_val>:<max_val>

Change-Id: Id202ca28bda44286deacb5c9969ffd92aa564a90
Signed-off-by: Michal Kelner Mishali <mkelnermishal@vmware.com>
This commit is contained in:
Michal Kelner Mishali 2018-02-25 15:36:38 +02:00
parent 895c25d2df
commit ab622863d5
5 changed files with 101 additions and 3 deletions

View File

@ -0,0 +1,9 @@
---
prelude: >
The NSX-V3 plugin can decide on the VLAN tag for a provider network.
features:
- |
The NSX-V3 plugin can decide on the VLAN tag for a provider network,
according to pre-defined configuration set per transport zone UUID,
noting a specific range or letting the plugin decide according to
min/max constants.

View File

@ -438,6 +438,12 @@ nsx_v3_opts = [
default=False,
help=_("(Optional) Indicates whether ENS transport zones can "
"be used")),
cfg.ListOpt('network_vlan_ranges',
default=[],
help=_("List of <TZ UUID>:<vlan_min>:<vlan_max> "
"specifying Transport Zone UUID usable for VLAN "
"provider networks, as well as ranges of VLAN "
"tags on each available for allocation to networks.")),
]
DEFAULT_STATUS_CHECK_INTERVAL = 2000

View File

@ -51,6 +51,13 @@ def get_network_bindings(session, network_id):
all())
def get_network_bindings_by_phy_uuid(session, phy_uuid):
session = session or db.get_reader_session()
return (session.query(nsx_models.TzNetworkBinding).
filter_by(phy_uuid=phy_uuid).
all())
def get_network_bindings_by_vlanid_and_physical_net(session, vlan_id,
phy_uuid):
session = session or db.get_reader_session()

View File

@ -81,6 +81,7 @@ from oslo_log import log
from oslo_utils import excutils
from oslo_utils import importutils
from oslo_utils import uuidutils
from six import moves
from sqlalchemy import exc as sql_exc
import webob.exc
@ -249,6 +250,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self.cfg_group = 'nsx_v3' # group name for nsx_v3 section in nsx.ini
self.tier0_groups_dict = {}
self._network_vlans = n_utils.parse_network_vlan_ranges(
cfg.CONF.nsx_v3.network_vlan_ranges)
# Initialize the network availability zones, which will be used only
# when native_dhcp_metadata is True
self.init_availability_zones()
@ -803,9 +806,9 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
# Validate VLAN id
if not vlan_id:
err_msg = (_('Segmentation ID must be specified with %s '
'network type') %
utils.NsxV3NetworkTypes.VLAN)
vlan_id = self._generate_segment_id(context,
physical_net,
network_data)
elif not n_utils.is_valid_vlan_tag(vlan_id):
err_msg = (_('Segmentation ID %(segmentation_id)s out of '
'range (%(min_id)s through %(max_id)s)') %
@ -1043,6 +1046,26 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return (bindings[0].binding_type ==
utils.NsxV3NetworkTypes.NSX_NETWORK)
def _generate_segment_id(self, context, physical_network, net_data):
bindings = nsx_db.get_network_bindings_by_phy_uuid(
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 = const.MIN_VLAN_TAG
vlan_max = const.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[pnet.SEGMENTATION_ID] = free_ids[0]
return net_data[pnet.SEGMENTATION_ID]
def create_network(self, context, network):
net_data = network['network']
external = net_data.get(extnet_apidef.EXTERNAL)

View File

@ -19,6 +19,7 @@ from neutron.db import models_v2
from neutron.extensions import address_scope
from neutron.extensions import l3
from neutron.extensions import securitygroup as secgrp
from neutron.plugins.common import utils as n_utils
from neutron.tests.unit import _test_extension_portbindings as test_bindings
from neutron.tests.unit.db import test_db_base_plugin_v2 as test_plugin
from neutron.tests.unit.extensions import test_address_scope
@ -29,6 +30,7 @@ from neutron.tests.unit.extensions \
import test_l3_ext_gw_mode as test_ext_gw_mode
from neutron.tests.unit.scheduler \
import test_dhcp_agent_scheduler as test_dhcpagent
from neutron.tests.unit import testlib_api
from neutron_lib.api.definitions import external_net as extnet_apidef
from neutron_lib.api.definitions import extraroute as xroute_apidef
from neutron_lib.api.definitions import l3_ext_gw_mode as l3_egm_apidef
@ -546,6 +548,57 @@ class TestNetworksV2(test_plugin.TestNetworksV2, NsxV3PluginTestCaseMixin):
data = self.deserialize('json', result)
self.assertEqual('vlan', data['network'].get(pnet.NETWORK_TYPE))
def _test_generate_tag(self, vlan_id):
net_type = 'vlan'
name = 'phys_net'
plugin = directory.get_plugin()
plugin._network_vlans = n_utils.parse_network_vlan_ranges(
cfg.CONF.nsx_v3.network_vlan_ranges)
expected = [('subnets', []), ('name', name),
('admin_state_up', True),
('status', 'ACTIVE'),
('shared', False),
(pnet.NETWORK_TYPE, net_type),
(pnet.PHYSICAL_NETWORK,
'fb69d878-958e-4f32-84e4-50286f26226b'),
(pnet.SEGMENTATION_ID, vlan_id)]
providernet_args = {pnet.NETWORK_TYPE: net_type,
pnet.PHYSICAL_NETWORK:
'fb69d878-958e-4f32-84e4-50286f26226b'}
with mock.patch('vmware_nsxlib.v3.core_resources.NsxLibTransportZone.'
'get_transport_type', return_value='VLAN'):
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_phys_vlan_generate(self):
cfg.CONF.set_override('network_vlan_ranges',
'fb69d878-958e-4f32-84e4-50286f26226b',
'nsx_v3')
self._test_generate_tag(1)
def test_create_phys_vlan_generate_range(self):
cfg.CONF.set_override('network_vlan_ranges',
'fb69d878-958e-4f32-84e4-'
'50286f26226b:100:110',
'nsx_v3')
self._test_generate_tag(100)
def test_create_phys_vlan_network_outofrange_returns_503(self):
cfg.CONF.set_override('network_vlan_ranges',
'fb69d878-958e-4f32-84e4-'
'50286f26226b:9:10',
'nsx_v3')
self._test_generate_tag(9)
self._test_generate_tag(10)
with testlib_api.ExpectedException(exc.HTTPClientError) as ctx_manager:
self._test_generate_tag(11)
self.assertEqual(ctx_manager.exception.code, 503)
class TestSubnetsV2(test_plugin.TestSubnetsV2, NsxV3PluginTestCaseMixin):