diff --git a/networking_aci/plugins/ml2/drivers/mech_aci/allocations_manager.py b/networking_aci/plugins/ml2/drivers/mech_aci/allocations_manager.py index c83c2f7..5bb90e6 100644 --- a/networking_aci/plugins/ml2/drivers/mech_aci/allocations_manager.py +++ b/networking_aci/plugins/ml2/drivers/mech_aci/allocations_manager.py @@ -12,6 +12,8 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +from itertools import groupby +from operator import itemgetter import os import random import time @@ -369,7 +371,7 @@ def _sync_allocations(self): level = 1 # Currently only supporting one level in hierarchy segment_type = 'vlan' - config_hg_names = list(ACI_CONFIG.hostgroups) + config_physnets = {hg['physical_network'] for hg in ACI_CONFIG.hostgroups.values()} ctx = context.get_admin_context() with db_api.CONTEXT_WRITER.using(ctx) as session: # fetch allocations from db @@ -385,7 +387,7 @@ def _sync_allocations(self): hosts_to_delete = set() # orphaned hosts with deletable allocs (no network_id assigned) for n, db_alloc in enumerate(db_allocs): if db_alloc.host not in allocations: - if db_alloc.host not in config_hg_names: + if db_alloc.host not in config_physnets: if db_alloc.network_id is None: # at least one allocation with this host can be deleted hosts_to_delete.add(db_alloc.host) @@ -398,28 +400,32 @@ def _sync_allocations(self): # sync existing hostgroups (remove out-of-range allocs, add new allocs) LOG.debug("Processing hostgroups") - for hg_name, hg_config in ACI_CONFIG.hostgroups.items(): - if hg_config['direct_mode']: - continue - - hg_vlans = self._segmentation_ids(hg_config) - if hg_name in allocations: + hostgroups = [hg for hg in ACI_CONFIG.hostgroups.values() if not hg['direct_mode']] + hostgroups.sort(key=itemgetter('physical_network')) + for hg_physnet, hg_configs in groupby(hostgroups, key=itemgetter('physical_network')): + hg_vlans = set() + for hg_config in hg_configs: + hg_vlans |= self._segmentation_ids(hg_config) + + if hg_physnet in allocations: # delete extra allocs - out_of_range_ids = no_net_allocations[hg_name] - hg_vlans + out_of_range_ids = no_net_allocations[hg_physnet] - hg_vlans if out_of_range_ids: - LOG.debug("Deleting %s out-of-range allocations with no assigned network for hostgroup %s", - len(out_of_range_ids), hg_name) + LOG.debug("Deleting %s out-of-range allocations with no assigned network for " + "physical network %s", + len(out_of_range_ids), hg_physnet) # delete all extra allocations that don't have a network_id referenced del_q = session.query(AllocationsModel) - del_q = del_q.filter_by(level=level, segment_type=segment_type, host=hg_name, network_id=None) + del_q = del_q.filter_by(level=level, segment_type=segment_type, host=hg_physnet, + network_id=None) del_q = del_q.filter(AllocationsModel.segmentation_id.in_(out_of_range_ids)) del_q.delete() - missing_ids = hg_vlans - allocations.get(hg_name, set()) + missing_ids = hg_vlans - allocations.get(hg_physnet, set()) if missing_ids: - LOG.debug("Adding %s allocations for hostgroup %s", len(missing_ids), hg_name) + LOG.debug("Adding %s allocations for physical network %s", len(missing_ids), hg_physnet) for seg_id in missing_ids: - alloc = AllocationsModel(host=hg_name, level=level, segment_type=segment_type, + alloc = AllocationsModel(host=hg_physnet, level=level, segment_type=segment_type, segmentation_id=seg_id, network_id=None) session.add(alloc) diff --git a/networking_aci/plugins/ml2/drivers/mech_aci/config.py b/networking_aci/plugins/ml2/drivers/mech_aci/config.py index f0e6091..448c714 100644 --- a/networking_aci/plugins/ml2/drivers/mech_aci/config.py +++ b/networking_aci/plugins/ml2/drivers/mech_aci/config.py @@ -321,6 +321,8 @@ def _parse_hostgroups(self): for hostgroup_name, hostgroup in self._hostgroups.items(): hostgroup['name'] = hostgroup_name hostgroup['child_hostgroups'] = [] + if 'physical_network' not in hostgroup: + hostgroup['physical_network'] = hostgroup_name # handle segment ranges segment_ranges = hostgroup['segment_range'] diff --git a/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_allocations_manager.py b/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_allocations_manager.py index 1fa3746..d95862e 100644 --- a/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_allocations_manager.py +++ b/networking_aci/tests/unit/plugins/ml2/drivers/mech_aci/test_allocations_manager.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import itertools import os from pathlib import Path import tempfile @@ -77,18 +78,21 @@ def test_sync_allocations(self): hostgroups = { 'seagull': { 'direct_mode': False, + 'physical_network': 'seagull', 'hosts': [], 'segment_range': [(42, 45)], 'segment_type': 'vlan', }, 'oystercatcher': { 'direct_mode': False, + 'physical_network': 'oystercatcher', 'hosts': [], 'segment_range': [(100, 103)], 'segment_type': 'vlan', }, 'sparrow': { 'direct_mode': True, + 'physical_network': 'sparrow', 'hosts': [], 'segment_range': [(123, 234)], 'segment_type': 'vlan', @@ -131,3 +135,60 @@ def test_sync_allocations_locking(self): sync_db_mock.assert_not_called() self.assertEqual(open(sync_file).read(), str(os.getpid())) + + def test_sync_allocations_split_hg_same_physnet(self): + expected_gulls = [('seagull', vid, None) + for vid in itertools.chain(range(42, 46), range(100, 104), range(123, 127))] + expected_jays = [('jay', vid, None) for vid in range(200, 203)] + expected_result = expected_gulls + expected_jays + + db = common.DBPlugin() + ACI_CONFIG.db = db + + hostgroups = { + 'herring-gull': { + 'direct_mode': False, + 'hosts': [], + 'physical_network': 'seagull', + 'segment_range': [(42, 45)], + 'segment_type': 'vlan', + }, + 'yellow-legged-gull': { + 'direct_mode': False, + 'hosts': [], + 'physical_network': 'seagull', + 'segment_range': [(100, 103)], + 'segment_type': 'vlan', + }, + 'seagull': { + 'direct_mode': False, + 'physical_network': 'seagull', + 'hosts': [], + 'segment_range': [(123, 126)], + 'segment_type': 'vlan', + }, + 'blue-jay': { + 'direct_mode': False, + 'hosts': [], + 'physical_network': 'jay', + 'segment_range': [(200, 202)], + 'segment_type': 'vlan', + }, + 'diadem-jay': { + 'direct_mode': False, + 'hosts': [], + 'physical_network': 'jay', + 'segment_range': [(200, 202)], + 'segment_type': 'vlan', + }, + } + + with mock.patch('networking_aci.plugins.ml2.drivers.mech_aci.config.ACIConfig.hostgroups', + new_callable=mock.PropertyMock, return_value=hostgroups): + AllocationsManager(db) + + ctx = context.get_admin_context() + with db_api.CONTEXT_READER.using(ctx) as sess: + db_result = [(a.host, a.segmentation_id, a.network_id) for a in sess.query(AllocationsModel).all()] + + self.assertEqual(sorted(expected_result), sorted(db_result))