Skip to content

Commit 68079fa

Browse files
authored
Merge pull request #3 from fzakfeld/feat/l2-vnis
Add capability for L2 VNIs
2 parents ecdb587 + a3cea67 commit 68079fa

3 files changed

Lines changed: 55 additions & 6 deletions

File tree

osism/tasks/conductor/netbox.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,18 @@ def get_device_vlans(device):
160160
{
161161
'vlans': {vid: {'name': name, 'description': desc}},
162162
'vlan_members': {vid: {'port_name': 'tagging_mode'}},
163-
'vlan_interfaces': {vid: {'addresses': [ip_with_prefix, ...]}}
163+
'vlan_interfaces': {vid: {'addresses': [ip_with_prefix, ...]}},
164+
'l2vni_vlans': {vid: vni} -- VLANs tagged evpn-l2vni (VNI == VID)
164165
}
165166
"""
166167
from .sonic.cache import get_cached_device_interfaces
168+
from .sonic.constants import EVPN_L2VNI_TAG
167169

168170
vlans = {}
169171
vlan_members = {}
170172
vlan_interfaces = {}
173+
# Map of NetBox VLAN object id -> vid, collected while iterating interfaces
174+
vlan_obj_ids = {}
171175

172176
try:
173177
# Use cached interfaces instead of separate query
@@ -204,6 +208,7 @@ def get_device_vlans(device):
204208
"name": vlan.name or f"Vlan{vid}",
205209
"description": vlan.description or "",
206210
}
211+
vlan_obj_ids[vlan.id] = vid
207212

208213
# Add interface to VLAN members as untagged
209214
if vid not in vlan_members:
@@ -223,6 +228,7 @@ def get_device_vlans(device):
223228
"name": vlan.name or f"Vlan{vid}",
224229
"description": vlan.description or "",
225230
}
231+
vlan_obj_ids[vlan.id] = vid
226232

227233
# Add interface to VLAN members as tagged
228234
if vid not in vlan_members:
@@ -269,13 +275,33 @@ def get_device_vlans(device):
269275
# Skip if interface name doesn't follow Vlan<number> pattern
270276
pass
271277

278+
# Determine which VLANs are tagged evpn-l2vni by fetching full VLAN objects
279+
l2vni_vlans = {}
280+
if vlan_obj_ids:
281+
try:
282+
full_vlans = list(
283+
utils.nb.ipam.vlans.filter(id=list(vlan_obj_ids.keys()))
284+
)
285+
for v in full_vlans:
286+
if any(
287+
getattr(t, "slug", None) == EVPN_L2VNI_TAG
288+
for t in getattr(v, "tags", [])
289+
):
290+
l2vni_vlans[v.vid] = v.vid # VNI equals VID
291+
logger.debug(
292+
f"VLAN {v.vid} tagged {EVPN_L2VNI_TAG}, will add L2 VXLAN_TUNNEL_MAP entry"
293+
)
294+
except Exception as e:
295+
logger.warning(f"Could not fetch VLAN tags for L2 VNI check: {e}")
296+
272297
except Exception as e:
273298
logger.warning(f"Could not get VLANs for device {device.name}: {e}")
274299

275300
return {
276301
"vlans": vlans,
277302
"vlan_members": vlan_members,
278303
"vlan_interfaces": vlan_interfaces,
304+
"l2vni_vlans": l2vni_vlans,
279305
}
280306

281307

osism/tasks/conductor/sonic/config_generator.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ def generate_sonic_config(device, hwsku, device_as_mapping=None, config_version=
288288
_add_portchannel_configuration(config, portchannel_info, evpn_system_mac)
289289

290290
# Add VRF configuration
291-
_add_vrf_configuration(config, vrf_info, netbox_interfaces)
291+
_add_vrf_configuration(config, vrf_info, vlan_info, netbox_interfaces)
292292

293293
# Set DATABASE VERSION from config_version parameter or default
294294
if "VERSIONS" not in config:
@@ -1978,12 +1978,13 @@ def _get_vrf_info(device):
19781978
return vrf_info
19791979

19801980

1981-
def _add_vrf_configuration(config, vrf_info, netbox_interfaces):
1981+
def _add_vrf_configuration(config, vrf_info, vlan_info, netbox_interfaces):
19821982
"""Add VRF configuration to config.
19831983
19841984
Args:
19851985
config: Configuration dictionary to update
19861986
vrf_info: VRF information dictionary from _get_vrf_info()
1987+
vlan_info: VLAN information dictionary from get_device_vlans()
19871988
netbox_interfaces: Dict mapping SONiC names to NetBox interface info
19881989
"""
19891990
# Track VRFs with VNI for VXLAN configuration
@@ -2070,8 +2071,11 @@ def _add_vrf_configuration(config, vrf_info, netbox_interfaces):
20702071
config["BGP_GLOBALS"][vrf_name] = copy.deepcopy(default_bgp)
20712072
logger.info(f"Added BGP_GLOBALS for VRF {vrf_name}")
20722073

2073-
# Add VXLAN configuration if there are VRFs with VNI
2074-
if vrfs_with_vni:
2074+
# Collect L2 VNI VLANs (tagged evpn-l2vni in NetBox, VNI == VID)
2075+
l2vni_vlans = vlan_info.get("l2vni_vlans", {})
2076+
2077+
# Add VXLAN configuration if there are VRFs with VNI or L2 VNI VLANs
2078+
if vrfs_with_vni or l2vni_vlans:
20752079
# Get source IP from BGP_GLOBALS default router_id
20762080
src_ip = config.get("BGP_GLOBALS", {}).get("default", {}).get("router_id", "")
20772081

@@ -2090,7 +2094,7 @@ def _add_vrf_configuration(config, vrf_info, netbox_interfaces):
20902094
}
20912095
logger.info(f"Added VXLAN_EVPN_NVO nvo1 with source_vtep {VXLAN_VTEP_NAME}")
20922096

2093-
# Add VXLAN_TUNNEL_MAP for each VRF with VNI
2097+
# Add VXLAN_TUNNEL_MAP for each VRF with VNI (L3 / IRB)
20942098
for vrf_entry in vrfs_with_vni:
20952099
vni = vrf_entry["vni"]
20962100
vlan_name = f"Vlan{vni}"
@@ -2101,6 +2105,22 @@ def _add_vrf_configuration(config, vrf_info, netbox_interfaces):
21012105
}
21022106
logger.info(f"Added VXLAN_TUNNEL_MAP {map_key}")
21032107

2108+
# Add VXLAN_TUNNEL_MAP for each L2 VNI VLAN (pure L2, no VRF assignment)
2109+
vrf_vnis = {entry["vni"] for entry in vrfs_with_vni}
2110+
for vid, vni in l2vni_vlans.items():
2111+
if vni in vrf_vnis:
2112+
logger.debug(
2113+
f"Skipping L2 VNI {vni} for Vlan{vid}: already covered by VRF tunnel map"
2114+
)
2115+
continue
2116+
vlan_name = f"Vlan{vid}"
2117+
map_key = f"{VXLAN_VTEP_NAME}|map_{vni}_{vlan_name}"
2118+
config["VXLAN_TUNNEL_MAP"][map_key] = {
2119+
"vlan": vlan_name,
2120+
"vni": str(vni),
2121+
}
2122+
logger.info(f"Added L2 VXLAN_TUNNEL_MAP {map_key}")
2123+
21042124
# Add VRF assignments to interfaces
21052125
for sonic_interface, vrf_name in vrf_info["interface_vrf_mapping"].items():
21062126
# Check if this is a regular interface

osism/tasks/conductor/sonic/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
# Tag to enable EVPN Multihoming (evpn-lag mode) on a port channel
99
EVPN_LAG_TAG = "evpn-lag"
1010

11+
# Tag to enable L2 VxLAN (EVPN L2 VNI) for a VLAN — VNI equals VLAN ID
12+
EVPN_L2VNI_TAG = "evpn-l2vni"
13+
1114
# Default AS prefix for local ASN calculation
1215
DEFAULT_LOCAL_AS_PREFIX = 4200
1316

0 commit comments

Comments
 (0)