Skip to content

Commit

Permalink
Merge pull request #3882 from vyos/mergify/bp/sagitta/pr-3756
Browse files Browse the repository at this point in the history
vxlan: T6505: Support VXLAN VLAN-VNI range mapping in CLI (backport #3756)
  • Loading branch information
dmbaturin authored Jul 26, 2024
2 parents 24881b0 + a808139 commit d7f73cd
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 10 deletions.
28 changes: 24 additions & 4 deletions interface-definitions/interfaces_vxlan.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,35 @@
<format>u32:0-4094</format>
<description>Virtual Local Area Network (VLAN) ID</description>
</valueHelp>
<valueHelp>
<format>&lt;start-end&gt;</format>
<description>VLAN IDs range (use '-' as delimiter)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-4094"/>
<validator name="numeric" argument="--allow-range --range 0-4094"/>
</constraint>
<constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage>
<constraintErrorMessage>Not a valid VLAN ID or range, VLAN ID must be between 0 and 4094</constraintErrorMessage>
</properties>
<children>
#include <include/vni.xml.i>
<leafNode name="vni">
<properties>
<help>Virtual Network Identifier</help>
<valueHelp>
<format>u32:0-16777214</format>
<description>VXLAN virtual network identifier</description>
</valueHelp>
<valueHelp>
<format>&lt;start-end&gt;</format>
<description>VXLAN virtual network IDs range (use '-' as delimiter)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--allow-range --range 0-16777214"/>
</constraint>
<constraintErrorMessage>Not a valid VXLAN virtual network ID or range</constraintErrorMessage>
</properties>
</leafNode>
</children>
</tagNode>
</tagNode>
</children>
</tagNode>
</children>
Expand Down
20 changes: 17 additions & 3 deletions python/vyos/ifconfig/vxlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,19 @@ def set_vlan_vni_mapping(self, state):
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')

Expand All @@ -142,7 +155,7 @@ def set_vlan_vni_mapping(self, state):
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']
Expand All @@ -159,10 +172,11 @@ def set_vlan_vni_mapping(self, state):

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
Expand Down
47 changes: 47 additions & 0 deletions smoketest/scripts/cli/test_interfaces_vxlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
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):
Expand Down Expand Up @@ -153,6 +160,11 @@ def test_vxlan_vlan_vni_mapping(self):
'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])

Expand Down Expand Up @@ -185,6 +197,26 @@ def test_vxlan_vlan_vni_mapping(self):
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):
Expand Down Expand Up @@ -287,6 +319,12 @@ def test_vxlan_vni_filter_add_remove(self):
'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 ...
Expand All @@ -313,6 +351,15 @@ def test_vxlan_vni_filter_add_remove(self):
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__':
Expand Down
29 changes: 26 additions & 3 deletions src/conf_mode/interfaces_vxlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit d7f73cd

Please sign in to comment.