From 8adac1ac3ebaaeb5d77fa361adf8935ac40eeedf Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Mon, 6 Apr 2020 13:19:57 +0000 Subject: [PATCH] Default (shared) network segment range is not mandatory When "network_segment_range" service extension is enabled, the default (shared) network segment range could not exist. In this case, when retrieving the segmentation IDs, the existance of this range should be checked first. Conflicts: neutron/objects/network_segment_range.py Change-Id: Iaff891a48adc811ab114fb03b24ab3da9311eec3 Closes-Bug: #1870569 (cherry picked from commit 2e6aa290a38798f0eef04231d8424237daf3b2d4) (cherry picked from commit 222beb3a8d8ea38b5e2278e8387785270c1479d2) --- neutron/objects/network_segment_range.py | 22 ++++--- .../objects/test_network_segment_range.py | 57 +++++++++++++++++-- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/neutron/objects/network_segment_range.py b/neutron/objects/network_segment_range.py index 6614fa00839..79d57f134d5 100644 --- a/neutron/objects/network_segment_range.py +++ b/neutron/objects/network_segment_range.py @@ -188,15 +188,19 @@ class NetworkSegmentRange(base.NeutronDbObject): _filters = copy.deepcopy(filters) project_id = _filters.pop('project_id', None) with cls.db_context_reader(context): - # Retrieve default segment ID range. - default_range = context.session.query(cls.db_model).filter( + # Retrieve all network segment ranges shared. + shared_ranges = context.session.query(cls.db_model).filter( and_(cls.db_model.network_type == network_type, - cls.db_model.default == sql.expression.true())) + cls.db_model.shared == sql.expression.true())) if network_type == constants.TYPE_VLAN: - default_range.filter(cls.db_model.physical_network == + shared_ranges.filter(cls.db_model.physical_network == _filters['physical_network']) - segment_ids = set(six_range(default_range.all()[0].minimum, - default_range.all()[0].maximum + 1)) + segment_ids = set([]) + for shared_range in shared_ranges.all(): + segment_ids.update(set(six_range(shared_range.minimum, + shared_range.maximum + 1))) + if not segment_ids: + return [] # Retrieve other project segment ID ranges (not own project, not # default range). @@ -226,8 +230,8 @@ class NetworkSegmentRange(base.NeutronDbObject): # assigned to other projects. query = cls._build_query_segments(context, model, network_type, **_filters) - clauses = [and_(model_segmentation_id >= range[0], - model_segmentation_id <= range[1]) - for range in segment_ranges] + clauses = [and_(model_segmentation_id >= _range[0], + model_segmentation_id <= _range[1]) + for _range in segment_ranges] query = query.filter(or_(*clauses)) return query.limit(common_constants.IDPOOL_SELECT_SIZE).all() diff --git a/neutron/tests/unit/objects/test_network_segment_range.py b/neutron/tests/unit/objects/test_network_segment_range.py index 63354c6a4b0..1e4736b8369 100644 --- a/neutron/tests/unit/objects/test_network_segment_range.py +++ b/neutron/tests/unit/objects/test_network_segment_range.py @@ -107,14 +107,14 @@ class NetworkSegmentRangeDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, def _create_network_segment_range( self, minimum, maximum, network_type=None, physical_network=None, - project_id=None, default=False): + 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': 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) @@ -180,7 +180,7 @@ class NetworkSegmentRangeDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, obj.shared = False self.assertRaises(n_exc.ObjectActionError, obj.update) - def _create_environment(self): + 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], @@ -193,15 +193,31 @@ class NetworkSegmentRangeDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, 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).create() + 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)) @@ -275,3 +291,36 @@ class NetworkSegmentRangeDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, # 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)