f01f3ae5dd
In case when vlan network was created with segmentation_id=0 and without physical_network given, it was passing validation of provider segment and first available segmentation_id was choosen for network. Problem was that in such case all available segmentation ids where allocated and no other vlan network could be created later. This patch fixes validation of segmentation_id when it is set to value 0. Change-Id: Ic768deb84d544db832367f9a4b84a92729eee620 Closes-bug: #1840895
374 lines
16 KiB
Python
374 lines
16 KiB
Python
# Copyright (c) 2014 Thales Services SAS
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import mock
|
|
from neutron_lib import constants as p_const
|
|
from neutron_lib import context
|
|
from neutron_lib.db import api as db_api
|
|
from neutron_lib import exceptions as exc
|
|
from neutron_lib.plugins.ml2 import api
|
|
from neutron_lib.plugins import utils as plugin_utils
|
|
from oslo_config import cfg
|
|
from testtools import matchers
|
|
|
|
from neutron.objects import network_segment_range as obj_network_segment_range
|
|
from neutron.objects.plugins.ml2 import vlanallocation as vlan_alloc_obj
|
|
from neutron.plugins.ml2.drivers import type_vlan
|
|
from neutron.tests.unit import testlib_api
|
|
|
|
PROVIDER_NET = 'phys_net1'
|
|
TENANT_NET = 'phys_net2'
|
|
VLAN_MIN = 200
|
|
VLAN_MAX = 209
|
|
NETWORK_VLAN_RANGES = [PROVIDER_NET, "%s:%s:%s" %
|
|
(TENANT_NET, VLAN_MIN, VLAN_MAX)]
|
|
UPDATED_VLAN_RANGES = {
|
|
PROVIDER_NET: [],
|
|
TENANT_NET: [(VLAN_MIN + 5, VLAN_MAX + 5)],
|
|
}
|
|
EMPTY_VLAN_RANGES = {
|
|
PROVIDER_NET: []
|
|
}
|
|
CORE_PLUGIN = 'ml2'
|
|
SERVICE_PLUGIN_KLASS = ('neutron.services.network_segment_range.plugin.'
|
|
'NetworkSegmentRangePlugin')
|
|
|
|
|
|
class VlanTypeTest(testlib_api.SqlTestCase):
|
|
|
|
def setUp(self):
|
|
super(VlanTypeTest, self).setUp()
|
|
cfg.CONF.set_override('network_vlan_ranges',
|
|
NETWORK_VLAN_RANGES,
|
|
group='ml2_type_vlan')
|
|
self.network_vlan_ranges = plugin_utils.parse_network_vlan_ranges(
|
|
NETWORK_VLAN_RANGES)
|
|
self.driver = type_vlan.VlanTypeDriver()
|
|
self.driver._sync_vlan_allocations()
|
|
self.context = context.Context()
|
|
self.driver.physnet_mtus = []
|
|
self.setup_coreplugin(CORE_PLUGIN)
|
|
|
|
def test_parse_network_exception_handling(self):
|
|
with mock.patch.object(plugin_utils,
|
|
'parse_network_vlan_ranges') as parse_ranges:
|
|
parse_ranges.side_effect = Exception('any exception')
|
|
self.assertRaises(SystemExit,
|
|
self.driver._parse_network_vlan_ranges)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def _get_allocation(self, context, segment):
|
|
return vlan_alloc_obj.VlanAllocation.get_object(
|
|
context,
|
|
physical_network=segment[api.PHYSICAL_NETWORK],
|
|
vlan_id=segment[api.SEGMENTATION_ID])
|
|
|
|
def test_partial_segment_is_partial_segment(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN}
|
|
self.assertTrue(self.driver.is_partial_segment(segment))
|
|
|
|
def test_specific_segment_is_not_partial_segment(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: PROVIDER_NET,
|
|
api.SEGMENTATION_ID: 1}
|
|
self.assertFalse(self.driver.is_partial_segment(segment))
|
|
|
|
def test_validate_provider_segment(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: PROVIDER_NET,
|
|
api.SEGMENTATION_ID: 1}
|
|
self.assertIsNone(self.driver.validate_provider_segment(segment))
|
|
|
|
def test_validate_provider_segment_without_segmentation_id(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: TENANT_NET}
|
|
self.driver.validate_provider_segment(segment)
|
|
|
|
def test_validate_provider_segment_without_physical_network(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN}
|
|
self.driver.validate_provider_segment(segment)
|
|
|
|
def test_validate_provider_segment_no_phys_network_seg_id_0(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.SEGMENTATION_ID: 0}
|
|
self.assertRaises(exc.InvalidInput,
|
|
self.driver.validate_provider_segment,
|
|
segment)
|
|
|
|
def test_validate_provider_segment_with_missing_physical_network(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.SEGMENTATION_ID: 1}
|
|
self.assertRaises(exc.InvalidInput,
|
|
self.driver.validate_provider_segment,
|
|
segment)
|
|
|
|
def test_validate_provider_segment_with_invalid_physical_network(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: 'other_phys_net',
|
|
api.SEGMENTATION_ID: 1}
|
|
self.assertRaises(exc.InvalidInput,
|
|
self.driver.validate_provider_segment,
|
|
segment)
|
|
|
|
def test_validate_provider_segment_with_invalid_segmentation_id(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: PROVIDER_NET}
|
|
segmentation_ids = [
|
|
p_const.MIN_VLAN_TAG - 1,
|
|
p_const.MAX_VLAN_TAG + 1]
|
|
for segmentation_id in segmentation_ids:
|
|
segment[api.SEGMENTATION_ID] = segmentation_id
|
|
self.assertRaises(exc.InvalidInput,
|
|
self.driver.validate_provider_segment,
|
|
segment)
|
|
|
|
def test_validate_provider_segment_with_invalid_input(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: PROVIDER_NET,
|
|
api.SEGMENTATION_ID: 1,
|
|
'invalid': 1}
|
|
self.assertRaises(exc.InvalidInput,
|
|
self.driver.validate_provider_segment,
|
|
segment)
|
|
|
|
def test_validate_provider_segment_with_physical_network_only(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: PROVIDER_NET}
|
|
self.assertRaises(exc.InvalidInput,
|
|
self.driver.validate_provider_segment,
|
|
segment)
|
|
|
|
def test_sync_vlan_allocations(self):
|
|
def check_in_ranges(network_vlan_ranges):
|
|
vlan_min, vlan_max = network_vlan_ranges[TENANT_NET][0]
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: TENANT_NET}
|
|
|
|
segment[api.SEGMENTATION_ID] = vlan_min - 1
|
|
self.assertIsNone(
|
|
self._get_allocation(self.context, segment))
|
|
segment[api.SEGMENTATION_ID] = vlan_max + 1
|
|
self.assertIsNone(
|
|
self._get_allocation(self.context, segment))
|
|
|
|
segment[api.SEGMENTATION_ID] = vlan_min
|
|
self.assertFalse(
|
|
self._get_allocation(self.context, segment).allocated)
|
|
segment[api.SEGMENTATION_ID] = vlan_max
|
|
self.assertFalse(
|
|
self._get_allocation(self.context, segment).allocated)
|
|
|
|
check_in_ranges(self.network_vlan_ranges)
|
|
self.driver.network_vlan_ranges = UPDATED_VLAN_RANGES
|
|
self.driver._sync_vlan_allocations()
|
|
check_in_ranges(UPDATED_VLAN_RANGES)
|
|
|
|
self.driver.network_vlan_ranges = EMPTY_VLAN_RANGES
|
|
self.driver._sync_vlan_allocations()
|
|
|
|
vlan_min, vlan_max = UPDATED_VLAN_RANGES[TENANT_NET][0]
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: TENANT_NET}
|
|
segment[api.SEGMENTATION_ID] = vlan_min
|
|
self.assertIsNone(
|
|
self._get_allocation(self.context, segment))
|
|
segment[api.SEGMENTATION_ID] = vlan_max
|
|
self.assertIsNone(
|
|
self._get_allocation(self.context, segment))
|
|
|
|
def test_reserve_provider_segment(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: PROVIDER_NET,
|
|
api.SEGMENTATION_ID: 101}
|
|
alloc = self._get_allocation(self.context, segment)
|
|
self.assertIsNone(alloc)
|
|
observed = self.driver.reserve_provider_segment(self.context, segment)
|
|
alloc = self._get_allocation(self.context, observed)
|
|
self.assertTrue(alloc.allocated)
|
|
|
|
def test_reserve_provider_segment_already_allocated(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: PROVIDER_NET,
|
|
api.SEGMENTATION_ID: 101}
|
|
observed = self.driver.reserve_provider_segment(self.context, segment)
|
|
self.assertRaises(exc.VlanIdInUse,
|
|
self.driver.reserve_provider_segment,
|
|
self.context,
|
|
observed)
|
|
|
|
def test_reserve_provider_segment_in_tenant_pools(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: TENANT_NET,
|
|
api.SEGMENTATION_ID: VLAN_MIN}
|
|
alloc = self._get_allocation(self.context, segment)
|
|
self.assertFalse(alloc.allocated)
|
|
observed = self.driver.reserve_provider_segment(self.context, segment)
|
|
alloc = self._get_allocation(self.context, observed)
|
|
self.assertTrue(alloc.allocated)
|
|
|
|
def test_reserve_provider_segment_without_segmentation_id(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: TENANT_NET}
|
|
observed = self.driver.reserve_provider_segment(self.context, segment)
|
|
alloc = self._get_allocation(self.context, observed)
|
|
self.assertTrue(alloc.allocated)
|
|
vlan_id = observed[api.SEGMENTATION_ID]
|
|
self.assertThat(vlan_id, matchers.GreaterThan(VLAN_MIN - 1))
|
|
self.assertThat(vlan_id, matchers.LessThan(VLAN_MAX + 1))
|
|
|
|
def test_reserve_provider_segment_without_physical_network(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN}
|
|
observed = self.driver.reserve_provider_segment(self.context, segment)
|
|
alloc = self._get_allocation(self.context, observed)
|
|
self.assertTrue(alloc.allocated)
|
|
vlan_id = observed[api.SEGMENTATION_ID]
|
|
self.assertThat(vlan_id, matchers.GreaterThan(VLAN_MIN - 1))
|
|
self.assertThat(vlan_id, matchers.LessThan(VLAN_MAX + 1))
|
|
self.assertEqual(TENANT_NET, observed[api.PHYSICAL_NETWORK])
|
|
|
|
def test_reserve_provider_segment_all_allocateds(self):
|
|
for __ in range(VLAN_MIN, VLAN_MAX + 1):
|
|
self.driver.allocate_tenant_segment(self.context)
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN}
|
|
self.assertRaises(exc.NoNetworkAvailable,
|
|
self.driver.reserve_provider_segment,
|
|
self.context,
|
|
segment)
|
|
|
|
def test_get_mtu(self):
|
|
cfg.CONF.set_override('global_physnet_mtu', 1475)
|
|
cfg.CONF.set_override('path_mtu', 1400, group='ml2')
|
|
self.driver.physnet_mtus = {'physnet1': 1450, 'physnet2': 1400}
|
|
self.assertEqual(1450, self.driver.get_mtu('physnet1'))
|
|
|
|
cfg.CONF.set_override('global_physnet_mtu', 1375)
|
|
cfg.CONF.set_override('path_mtu', 1400, group='ml2')
|
|
self.driver.physnet_mtus = {'physnet1': 1450, 'physnet2': 1400}
|
|
self.assertEqual(1375, self.driver.get_mtu('physnet1'))
|
|
|
|
cfg.CONF.set_override('global_physnet_mtu', 0)
|
|
cfg.CONF.set_override('path_mtu', 1400, group='ml2')
|
|
self.driver.physnet_mtus = {'physnet1': 1450, 'physnet2': 1400}
|
|
self.assertEqual(1450, self.driver.get_mtu('physnet1'))
|
|
|
|
cfg.CONF.set_override('global_physnet_mtu', 0)
|
|
cfg.CONF.set_override('path_mtu', 0, group='ml2')
|
|
self.driver.physnet_mtus = {}
|
|
self.assertEqual(0, self.driver.get_mtu('physnet1'))
|
|
|
|
def test_allocate_tenant_segment(self):
|
|
for __ in range(VLAN_MIN, VLAN_MAX + 1):
|
|
segment = self.driver.allocate_tenant_segment(self.context)
|
|
alloc = self._get_allocation(self.context, segment)
|
|
self.assertTrue(alloc.allocated)
|
|
vlan_id = segment[api.SEGMENTATION_ID]
|
|
self.assertThat(vlan_id, matchers.GreaterThan(VLAN_MIN - 1))
|
|
self.assertThat(vlan_id, matchers.LessThan(VLAN_MAX + 1))
|
|
self.assertEqual(TENANT_NET, segment[api.PHYSICAL_NETWORK])
|
|
|
|
def test_allocate_tenant_segment_no_available(self):
|
|
for __ in range(VLAN_MIN, VLAN_MAX + 1):
|
|
self.driver.allocate_tenant_segment(self.context)
|
|
segment = self.driver.allocate_tenant_segment(self.context)
|
|
self.assertIsNone(segment)
|
|
|
|
def test_release_segment(self):
|
|
segment = self.driver.allocate_tenant_segment(self.context)
|
|
self.driver.release_segment(self.context, segment)
|
|
alloc = self._get_allocation(self.context, segment)
|
|
self.assertFalse(alloc.allocated)
|
|
|
|
def test_release_segment_unallocated(self):
|
|
segment = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
|
|
api.PHYSICAL_NETWORK: PROVIDER_NET,
|
|
api.SEGMENTATION_ID: 101}
|
|
with mock.patch.object(type_vlan.LOG, 'warning') as log_warn:
|
|
self.driver.release_segment(self.context, segment)
|
|
log_warn.assert_called_once_with(
|
|
"No vlan_id %(vlan_id)s found on physical network "
|
|
"%(physical_network)s",
|
|
{'vlan_id': 101, 'physical_network': PROVIDER_NET})
|
|
|
|
|
|
class VlanTypeAllocationTest(testlib_api.SqlTestCase):
|
|
|
|
def test_allocate_tenant_segment_in_order_of_config(self):
|
|
ranges = NETWORK_VLAN_RANGES + ['phys_net3:20:30']
|
|
cfg.CONF.set_override('network_vlan_ranges',
|
|
ranges,
|
|
group='ml2_type_vlan')
|
|
driver = type_vlan.VlanTypeDriver()
|
|
driver.physnet_mtus = []
|
|
driver._sync_vlan_allocations()
|
|
# swap config order from DB order after sync has happened to
|
|
# ensure config order is followed and not DB order
|
|
cfg.CONF.set_override('network_vlan_ranges',
|
|
list(reversed(ranges)),
|
|
group='ml2_type_vlan')
|
|
driver._parse_network_vlan_ranges()
|
|
ctx = context.Context()
|
|
for vlan in range(11):
|
|
# all of physnet3 should be exhausted first
|
|
self.assertEqual(
|
|
{'network_type': 'vlan', 'physical_network': 'phys_net3',
|
|
'segmentation_id': mock.ANY, 'mtu': 1500},
|
|
driver.allocate_tenant_segment(ctx))
|
|
for vlan in range(10):
|
|
# then physnet2
|
|
self.assertEqual(
|
|
{'network_type': 'vlan', 'physical_network': 'phys_net2',
|
|
'segmentation_id': mock.ANY, 'mtu': 1500},
|
|
driver.allocate_tenant_segment(ctx))
|
|
# then nothing
|
|
self.assertFalse(driver.allocate_tenant_segment(ctx))
|
|
|
|
|
|
class VlanTypeTestWithNetworkSegmentRange(testlib_api.SqlTestCase):
|
|
|
|
def setUp(self):
|
|
super(VlanTypeTestWithNetworkSegmentRange, self).setUp()
|
|
cfg.CONF.set_override('network_vlan_ranges',
|
|
NETWORK_VLAN_RANGES,
|
|
group='ml2_type_vlan')
|
|
cfg.CONF.set_override('service_plugins', [SERVICE_PLUGIN_KLASS])
|
|
self.network_vlan_ranges = plugin_utils.parse_network_vlan_ranges(
|
|
NETWORK_VLAN_RANGES)
|
|
self.driver = type_vlan.VlanTypeDriver()
|
|
self.driver._sync_vlan_allocations()
|
|
self.context = context.Context()
|
|
self.setup_coreplugin(CORE_PLUGIN)
|
|
|
|
def test__populate_new_default_network_segment_ranges(self):
|
|
# _populate_new_default_network_segment_ranges will be called when
|
|
# the type driver initializes with `network_segment_range` loaded as
|
|
# one of the `service_plugins`
|
|
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
|
self.context)
|
|
self.assertEqual(1, len(ret))
|
|
network_segment_range = ret[0]
|
|
self.assertTrue(network_segment_range.default)
|
|
self.assertTrue(network_segment_range.shared)
|
|
self.assertIsNone(network_segment_range.project_id)
|
|
self.assertEqual(p_const.TYPE_VLAN, network_segment_range.network_type)
|
|
self.assertEqual(TENANT_NET, network_segment_range.physical_network)
|
|
self.assertEqual(VLAN_MIN, network_segment_range.minimum)
|
|
self.assertEqual(VLAN_MAX, network_segment_range.maximum)
|
|
|
|
def test__delete_expired_default_network_segment_ranges(self):
|
|
self.driver._delete_expired_default_network_segment_ranges()
|
|
ret = obj_network_segment_range.NetworkSegmentRange.get_objects(
|
|
self.context)
|
|
self.assertEqual(0, len(ret))
|