327 lines
15 KiB
Python
327 lines
15 KiB
Python
# Copyright (c) 2019 Intel Corporation.
|
|
#
|
|
# 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 itertools
|
|
import random
|
|
from unittest import mock
|
|
|
|
from neutron_lib import constants
|
|
from neutron_lib import exceptions as n_exc
|
|
from neutron_lib.utils import helpers
|
|
from oslo_utils import uuidutils
|
|
|
|
from neutron.objects import network as net_obj
|
|
from neutron.objects import network_segment_range
|
|
from neutron.objects.plugins.ml2 import base as ml2_base
|
|
from neutron.objects.plugins.ml2 import vlanallocation as vlan_alloc_obj
|
|
from neutron.tests.unit.objects import test_base as obj_test_base
|
|
from neutron.tests.unit import testlib_api
|
|
|
|
TEST_TENANT_ID = '46f70361-ba71-4bd0-9769-3573fd227c4b'
|
|
TEST_PHYSICAL_NETWORK = 'phys_net'
|
|
NUM_ALLOCATIONS = 3
|
|
|
|
|
|
class NetworkSegmentRangeIfaceObjectTestCase(
|
|
obj_test_base.BaseObjectIfaceTestCase):
|
|
|
|
_test_class = network_segment_range.NetworkSegmentRange
|
|
|
|
def setUp(self):
|
|
self._mock_get_available_allocation = mock.patch.object(
|
|
network_segment_range.NetworkSegmentRange,
|
|
'_get_available_allocation',
|
|
return_value=[])
|
|
self.mock_get_available_allocation = (
|
|
self._mock_get_available_allocation.start())
|
|
self._mock_get_used_allocation_mapping = mock.patch.object(
|
|
network_segment_range.NetworkSegmentRange,
|
|
'_get_used_allocation_mapping',
|
|
return_value={})
|
|
self.mock_get_used_allocation_mapping = (
|
|
self._mock_get_used_allocation_mapping.start())
|
|
super(NetworkSegmentRangeIfaceObjectTestCase, self).setUp()
|
|
# `project_id` and `physical_network` attributes in
|
|
# network_segment_range are nullable, depending on the value of
|
|
# `shared` and `network_type` respectively.
|
|
# Hack to always populate test project_id and physical_network
|
|
# fields in network segment range Iface object testing so that related
|
|
# tests like `test_create_updates_from_db_object` and
|
|
# `test_update_updates_from_db_object` can have those fields.
|
|
# Alternatives can be skipping those tests when executing
|
|
# NetworkSegmentRangeIfaceObjectTestCase, or making base test case
|
|
# adjustments.
|
|
self.update_obj_fields({'project_id': TEST_TENANT_ID,
|
|
'physical_network': TEST_PHYSICAL_NETWORK})
|
|
self.extra_fields_not_in_dict = ['tenant_id']
|
|
|
|
|
|
class NetworkSegmentRangeDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
|
testlib_api.SqlTestCase):
|
|
|
|
_test_class = network_segment_range.NetworkSegmentRange
|
|
|
|
def _create_allocation(self, allocation_class, segmentation_id=None,
|
|
physical_network=None, allocated=False):
|
|
attr = self.get_random_object_fields(allocation_class)
|
|
attr['allocated'] = allocated
|
|
allocation_class.update_primary_keys(
|
|
attr, segmentation_id=segmentation_id,
|
|
physical_network=physical_network or 'foo')
|
|
allocation = allocation_class(self.context, **attr)
|
|
allocation.create()
|
|
return allocation
|
|
|
|
def _create_test_network(self, name=None, network_id=None):
|
|
name = "test-network-%s" % helpers.get_random_string(4)
|
|
network_id = (uuidutils.generate_uuid() if network_id is None
|
|
else network_id)
|
|
_network = net_obj.Network(self.context, name=name, id=network_id,
|
|
project_id=uuidutils.generate_uuid())
|
|
_network.create()
|
|
return _network
|
|
|
|
def _create_segment(self, segmentation_id=None, network_id=None,
|
|
physical_network=None, network_type=None):
|
|
attr = self.get_random_object_fields(net_obj.NetworkSegment)
|
|
attr.update({
|
|
'network_id': network_id or self._create_test_network_id(),
|
|
'network_type': network_type or constants.TYPE_VLAN,
|
|
'physical_network': physical_network or 'foo',
|
|
'segmentation_id': segmentation_id or random.randint(
|
|
constants.MIN_VLAN_TAG, constants.MAX_VLAN_TAG)})
|
|
_segment = net_obj.NetworkSegment(self.context, **attr)
|
|
_segment.create()
|
|
return _segment
|
|
|
|
def _create_network_segment_range(
|
|
self, minimum, maximum, network_type=None, physical_network=None,
|
|
project_id=None, default=False, shared=False):
|
|
kwargs = self.get_random_db_fields()
|
|
kwargs.update({'network_type': network_type or constants.TYPE_VLAN,
|
|
'physical_network': physical_network or 'foo',
|
|
'minimum': minimum,
|
|
'maximum': maximum,
|
|
'default': default,
|
|
'shared': shared,
|
|
'project_id': project_id})
|
|
db_obj = self._test_class.db_model(**kwargs)
|
|
obj_fields = self._test_class.modify_fields_from_db(db_obj)
|
|
obj = self._test_class(self.context, **obj_fields)
|
|
return obj
|
|
|
|
def test__get_available_allocation(self):
|
|
range_minimum = 100
|
|
range_maximum = 120
|
|
to_alloc = range(range_minimum, range_maximum - 5)
|
|
not_to_alloc = range(range_maximum - 5, range_maximum + 1)
|
|
for vlan_id in to_alloc:
|
|
self._create_allocation(vlan_alloc_obj.VlanAllocation,
|
|
segmentation_id=vlan_id, allocated=True,
|
|
physical_network='foo')
|
|
for vlan_id in not_to_alloc:
|
|
self._create_allocation(vlan_alloc_obj.VlanAllocation,
|
|
segmentation_id=vlan_id, allocated=False,
|
|
physical_network='foo')
|
|
obj = self._create_network_segment_range(range_minimum, range_maximum)
|
|
available_alloc = self._test_class._get_available_allocation(obj)
|
|
self.assertItemsEqual(not_to_alloc, available_alloc)
|
|
|
|
def test__get_used_allocation_mapping(self):
|
|
alloc_mapping = {}
|
|
for _ in range(5):
|
|
network = self._create_test_network()
|
|
segment = self._create_segment(network_id=network.id)
|
|
alloc_mapping.update({segment.segmentation_id: network.project_id})
|
|
|
|
obj = self._create_network_segment_range(
|
|
minimum=min(list(alloc_mapping.keys())),
|
|
maximum=max(list(alloc_mapping.keys())))
|
|
ret_alloc_mapping = self._test_class._get_used_allocation_mapping(obj)
|
|
self.assertDictEqual(alloc_mapping, ret_alloc_mapping)
|
|
|
|
def _define_network_segment_range(self, shared=False,
|
|
remove_project_id=False):
|
|
attrs = self.get_random_object_fields(obj_cls=self._test_class)
|
|
obj = self._test_class(self.context, **attrs)
|
|
obj.shared = shared
|
|
obj.project_id = None if remove_project_id else obj.project_id
|
|
return obj
|
|
|
|
def test_create_not_shared_with_project_id(self):
|
|
obj = self._define_network_segment_range()
|
|
obj.create()
|
|
|
|
def test_create_not_shared_without_project_id(self):
|
|
obj = self._define_network_segment_range(remove_project_id=True)
|
|
self.assertRaises(n_exc.ObjectActionError, obj.create)
|
|
|
|
def test_update_not_shared_with_project_id(self):
|
|
obj = self._define_network_segment_range(shared=True)
|
|
obj.create()
|
|
obj.shared = False
|
|
obj.update()
|
|
|
|
def test_update_not_shared_without_project_id(self):
|
|
obj = self._define_network_segment_range(shared=True,
|
|
remove_project_id=True)
|
|
obj.create()
|
|
obj.shared = False
|
|
self.assertRaises(n_exc.ObjectActionError, obj.update)
|
|
|
|
def _create_environment(self, default_range=True):
|
|
self.projects = [uuidutils.generate_uuid() for _ in range(3)]
|
|
self.segment_ranges = {
|
|
'default': [100, 120], self.projects[0]: [90, 105],
|
|
self.projects[1]: [109, 114], self.projects[2]: [117, 130]}
|
|
self.seg_min = self.segment_ranges['default'][0]
|
|
self.seg_max = self.segment_ranges['default'][1]
|
|
|
|
for subclass in ml2_base.SegmentAllocation.__subclasses__():
|
|
# Build segment ranges: default one and project specific ones.
|
|
for name, ranges in self.segment_ranges.items():
|
|
default = True if name == 'default' else False
|
|
project = name if not default else None
|
|
if default and not default_range:
|
|
continue
|
|
|
|
self._create_network_segment_range(
|
|
ranges[0], ranges[1], network_type=subclass.network_type,
|
|
project_id=project, default=default,
|
|
shared=default).create()
|
|
|
|
# Build allocations (non allocated).
|
|
for segmentation_id in range(self.seg_min, self.seg_max + 1):
|
|
self._create_allocation(subclass,
|
|
segmentation_id=segmentation_id)
|
|
|
|
def _create_shared_ranges(self):
|
|
self.shared_ranges = {0: [100, 105], 1: [110, 115]}
|
|
self.shared_ids = set(itertools.chain.from_iterable(
|
|
list(range(r[0], r[1] + 1)) for r in self.shared_ranges.values()))
|
|
for shared_range, subclass in itertools.product(
|
|
self.shared_ranges.values(),
|
|
ml2_base.SegmentAllocation.__subclasses__()):
|
|
self._create_network_segment_range(
|
|
shared_range[0], shared_range[1],
|
|
network_type=subclass.network_type, default=False,
|
|
shared=True).create()
|
|
|
|
def _default_range_set(self, project_id=None):
|
|
range_set = set(range(self.segment_ranges['default'][0],
|
|
self.segment_ranges['default'][1] + 1))
|
|
for p_id, ranges in ((p, r) for (p, r) in self.segment_ranges.items()
|
|
if p not in [project_id, 'default']):
|
|
pranges = self.segment_ranges.get(p_id, [0, 0])
|
|
prange_set = set(range(pranges[0], pranges[1] + 1))
|
|
range_set.difference_update(prange_set)
|
|
return range_set
|
|
|
|
def _allocate_random_allocations(self, allocations, subclass):
|
|
pk_cols = subclass.db_model.__table__.primary_key.columns
|
|
primary_keys = [col.name for col in pk_cols]
|
|
allocated = []
|
|
for allocation in random.sample(allocations, k=NUM_ALLOCATIONS):
|
|
segment = dict((k, allocation[k]) for k in primary_keys)
|
|
allocated.append(segment)
|
|
self.assertEqual(1, subclass.allocate(self.context, **segment))
|
|
return allocated
|
|
|
|
def test_get_segments_for_project(self):
|
|
self._create_environment()
|
|
for project_id, subclass in itertools.product(
|
|
self.projects, ml2_base.SegmentAllocation.__subclasses__()):
|
|
allocations = network_segment_range.NetworkSegmentRange. \
|
|
get_segments_for_project(
|
|
self.context, subclass.db_model, subclass.network_type,
|
|
subclass.get_segmentation_id(), project_id=project_id)
|
|
project_min = max(self.seg_min, self.segment_ranges[project_id][0])
|
|
project_max = min(self.seg_max, self.segment_ranges[project_id][1])
|
|
project_segment_ids = list(range(project_min, project_max + 1))
|
|
self.assertEqual(len(allocations), len(project_segment_ids))
|
|
for allocation in allocations:
|
|
self.assertFalse(allocation.allocated)
|
|
self.assertIn(allocation.segmentation_id, project_segment_ids)
|
|
|
|
# Allocate random segments inside the project range.
|
|
self._allocate_random_allocations(allocations, subclass)
|
|
allocations = network_segment_range.NetworkSegmentRange. \
|
|
get_segments_for_project(
|
|
self.context, subclass.db_model, subclass.network_type,
|
|
subclass.get_segmentation_id(), project_id=project_id)
|
|
self.assertEqual(len(allocations),
|
|
len(project_segment_ids) - NUM_ALLOCATIONS)
|
|
|
|
def test_get_segments_shared(self):
|
|
self._create_environment()
|
|
self.projects.append(None)
|
|
for project_id, subclass in itertools.product(
|
|
self.projects, ml2_base.SegmentAllocation.__subclasses__()):
|
|
filters = {'project_id': project_id,
|
|
'physical_network': 'foo'}
|
|
allocations = network_segment_range.NetworkSegmentRange. \
|
|
get_segments_shared(
|
|
self.context, subclass.db_model, subclass.network_type,
|
|
subclass.get_segmentation_id(), **filters)
|
|
|
|
prange = self._default_range_set(project_id)
|
|
self.assertEqual(len(prange), len(allocations))
|
|
|
|
# Allocate random segments inside the project shared range.
|
|
allocated = self._allocate_random_allocations(allocations,
|
|
subclass)
|
|
allocations = network_segment_range.NetworkSegmentRange. \
|
|
get_segments_shared(
|
|
self.context, subclass.db_model, subclass.network_type,
|
|
subclass.get_segmentation_id(), **filters)
|
|
self.assertEqual(len(allocations), len(prange) - NUM_ALLOCATIONS)
|
|
|
|
# Deallocate the allocated segments because can be allocated in
|
|
# a segmentation ID not belonging to any project.
|
|
for alloc in allocated:
|
|
self.assertEqual(1, subclass.deallocate(self.context, **alloc))
|
|
|
|
def test_get_segments_shared_no_shared_ranges(self):
|
|
self._create_environment(default_range=False)
|
|
for project_id, subclass in itertools.product(
|
|
self.projects, ml2_base.SegmentAllocation.__subclasses__()):
|
|
filters = {'project_id': project_id,
|
|
'physical_network': 'foo'}
|
|
allocations = network_segment_range.NetworkSegmentRange. \
|
|
get_segments_shared(
|
|
self.context, subclass.db_model, subclass.network_type,
|
|
subclass.get_segmentation_id(), **filters)
|
|
|
|
self.assertEqual([], allocations)
|
|
|
|
def test_get_segments_shared_no_default_range_two_shared_ranges(self):
|
|
self._create_environment(default_range=False)
|
|
self.projects.append(None)
|
|
self._create_shared_ranges()
|
|
for project_id, subclass in itertools.product(
|
|
self.projects, ml2_base.SegmentAllocation.__subclasses__()):
|
|
|
|
filters = {'project_id': project_id,
|
|
'physical_network': 'foo'}
|
|
allocations = network_segment_range.NetworkSegmentRange. \
|
|
get_segments_shared(
|
|
self.context, subclass.db_model, subclass.network_type,
|
|
subclass.get_segmentation_id(), **filters)
|
|
|
|
prange = self._default_range_set(project_id)
|
|
available_ids = prange & self.shared_ids
|
|
self.assertEqual(len(available_ids), len(allocations))
|
|
for alloc in allocations:
|
|
self.assertIn(alloc.segmentation_id, available_ids)
|