From ce5460f0380a60eb07f70dfed29d1b011636448a Mon Sep 17 00:00:00 2001 From: Nataliia S <81954790+natali-rs1985@users.noreply.github.com> Date: Mon, 8 Jul 2024 18:51:40 +0300 Subject: vxlan: T6505: Support VXLAN VLAN-VNI range mapping in CLI (#3756) (cherry picked from commit 115e99630a317cab62c6f99e0461f6ce2c1edaf3) --- interface-definitions/interfaces_vxlan.xml.in | 28 ++++++++++++--- python/vyos/ifconfig/vxlan.py | 20 +++++++++-- smoketest/scripts/cli/test_interfaces_vxlan.py | 47 ++++++++++++++++++++++++++ src/conf_mode/interfaces_vxlan.py | 29 ++++++++++++++-- 4 files changed, 114 insertions(+), 10 deletions(-) diff --git a/interface-definitions/interfaces_vxlan.xml.in b/interface-definitions/interfaces_vxlan.xml.in index 504c08e7e..937acb123 100644 --- a/interface-definitions/interfaces_vxlan.xml.in +++ b/interface-definitions/interfaces_vxlan.xml.in @@ -117,15 +117,35 @@ u32:0-4094 Virtual Local Area Network (VLAN) ID + + <start-end> + VLAN IDs range (use '-' as delimiter) + - + - VLAN ID must be between 0 and 4094 + Not a valid VLAN ID or range, VLAN ID must be between 0 and 4094 - #include + + + Virtual Network Identifier + + u32:0-16777214 + VXLAN virtual network identifier + + + <start-end> + VXLAN virtual network IDs range (use '-' as delimiter) + + + + + Not a valid VXLAN virtual network ID or range + + - + diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 918aea202..1023c58d1 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -134,6 +134,19 @@ class VXLANIf(Interface): Controls whether vlan to tunnel mapping is enabled on the port. By default this flag is off. """ + def range_to_dict(vlan_to_vni): + """ Converts dict of ranges to dict """ + result_dict = {} + for vlan, vlan_conf in vlan_to_vni.items(): + vni = vlan_conf['vni'] + vlan_range, vni_range = vlan.split('-'), vni.split('-') + if len(vlan_range) > 1: + vlan_range = range(int(vlan_range[0]), int(vlan_range[1]) + 1) + vni_range = range(int(vni_range[0]), int(vni_range[1]) + 1) + dict_to_add = {str(k): {'vni': str(v)} for k, v in zip(vlan_range, vni_range)} + result_dict.update(dict_to_add) + return result_dict + if not isinstance(state, bool): raise ValueError('Value out of range') @@ -142,7 +155,7 @@ class VXLANIf(Interface): if dict_search('parameters.vni_filter', self.config) != None: cur_vni_filter = get_vxlan_vni_filter(self.ifname) - for vlan, vlan_config in self.config['vlan_to_vni_removed'].items(): + for vlan, vlan_config in range_to_dict(self.config['vlan_to_vni_removed']).items(): # If VNI filtering is enabled, remove matching VNI filter if cur_vni_filter != None: vni = vlan_config['vni'] @@ -159,10 +172,11 @@ class VXLANIf(Interface): if 'vlan_to_vni' in self.config: # Determine current OS Kernel configured VLANs + vlan_vni_mapping = range_to_dict(self.config['vlan_to_vni']) os_configured_vlan_ids = get_vxlan_vlan_tunnels(self.ifname) - add_vlan = list_diff(list(self.config['vlan_to_vni'].keys()), os_configured_vlan_ids) + add_vlan = list_diff(list(vlan_vni_mapping.keys()), os_configured_vlan_ids) - for vlan, vlan_config in self.config['vlan_to_vni'].items(): + for vlan, vlan_config in vlan_vni_mapping.items(): # VLAN mapping already exists - skip if vlan not in add_vlan: continue diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index 18676491b..b2076b43b 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -27,6 +27,13 @@ from vyos.utils.network import get_vxlan_vni_filter from vyos.template import is_ipv6 from base_interfaces_test import BasicInterfaceTest +def convert_to_list(ranges_to_convert): + result_list = [] + for r in ranges_to_convert: + ranges = r.split('-') + result_list.extend([str(i) for i in range(int(ranges[0]), int(ranges[1]) + 1)]) + return result_list + class VXLANInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): @@ -153,6 +160,11 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): '31': '10031', } + vlan_to_vni_ranges = { + '40-43': '10040-10043', + '45-47': '10045-10047' + } + self.cli_set(self._base_path + [interface, 'parameters', 'external']) self.cli_set(self._base_path + [interface, 'source-address', source_address]) @@ -185,6 +197,26 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): tmp = get_vxlan_vlan_tunnels('vxlan0') self.assertEqual(tmp, list(vlan_to_vni)) + # add ranged VLAN - VNI mapping + for vlan, vni in vlan_to_vni_ranges.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + self.cli_commit() + + tmp = get_vxlan_vlan_tunnels('vxlan0') + vlans_list = convert_to_list(vlan_to_vni_ranges.keys()) + self.assertEqual(tmp, list(vlan_to_vni) + vlans_list) + + # check validate() - cannot map VNI range to a single VLAN id + self.cli_set(self._base_path + [interface, 'vlan-to-vni', '100', 'vni', '100-102']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'vlan-to-vni', '100']) + + # check validate() - cannot map VLAN to VNI with different ranges + self.cli_set(self._base_path + [interface, 'vlan-to-vni', '100-102', 'vni', '100-105']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(['interfaces', 'bridge', bridge]) def test_vxlan_neighbor_suppress(self): @@ -287,6 +319,12 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): '60': '10060', '69': '10069', } + + vlan_to_vni_ranges = { + '70-73': '10070-10073', + '75-77': '10075-10077' + } + for vlan, vni in vlan_to_vni.items(): self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) # we need a bridge ... @@ -313,6 +351,15 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): tmp = get_vxlan_vni_filter(interface) self.assertListEqual(list(vlan_to_vni.values()), tmp) + # add ranged VLAN - VNI mapping + for vlan, vni in vlan_to_vni_ranges.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + self.cli_commit() + + tmp = get_vxlan_vni_filter(interface) + vnis_list = convert_to_list(vlan_to_vni_ranges.values()) + self.assertListEqual(list(vlan_to_vni.values()) + vnis_list, tmp) + self.cli_delete(['interfaces', 'bridge', bridge]) if __name__ == '__main__': diff --git a/src/conf_mode/interfaces_vxlan.py b/src/conf_mode/interfaces_vxlan.py index bc4918a52..68646e8ff 100755 --- a/src/conf_mode/interfaces_vxlan.py +++ b/src/conf_mode/interfaces_vxlan.py @@ -179,13 +179,36 @@ def verify(vxlan): 'is member of a bridge interface!') vnis_used = [] + vlans_used = [] for vif, vif_config in vxlan['vlan_to_vni'].items(): if 'vni' not in vif_config: raise ConfigError(f'Must define VNI for VLAN "{vif}"!') vni = vif_config['vni'] - if vni in vnis_used: - raise ConfigError(f'VNI "{vni}" is already assigned to a different VLAN!') - vnis_used.append(vni) + + err_msg = f'VLAN range "{vif}" does not match VNI range "{vni}"!' + vif_range, vni_range = list(map(int, vif.split('-'))), list(map(int, vni.split('-'))) + + if len(vif_range) != len(vni_range): + raise ConfigError(err_msg) + + if len(vif_range) > 1: + if vni_range[0] > vni_range[-1] or vif_range[0] > vif_range[-1]: + raise ConfigError('The upper bound of the range must be greater than the lower bound!') + vni_range = range(vni_range[0], vni_range[1] + 1) + vif_range = range(vif_range[0], vif_range[1] + 1) + + if len(vif_range) != len(vni_range): + raise ConfigError(err_msg) + + for vni_id in vni_range: + if vni_id in vnis_used: + raise ConfigError(f'VNI "{vni_id}" is already assigned to a different VLAN!') + vnis_used.append(vni_id) + + for vif_id in vif_range: + if vif_id in vlans_used: + raise ConfigError(f'VLAN "{vif_id}" is already in use!') + vlans_used.append(vif_id) if dict_search('parameters.neighbor_suppress', vxlan) != None: if 'is_bridge_member' not in vxlan: -- cgit v1.2.3