From 97fe7b1b8aaacdc38d52fb942d5407980b6443db Mon Sep 17 00:00:00 2001
From: Vibhu-gslab <109593615+Vibhu-gslab@users.noreply.github.com>
Date: Wed, 18 Sep 2024 16:57:57 +0530
Subject: [PATCH 1/3] Refactor(eos_designs): Use VRF ID instead of VRF VNI as
offset for evpn underlay l3 multicast group (#4450)
---
.../arista/avd/docs/porting-guides/5.x.x.md | 21 +++++++++++
.../arista/avd/docs/release-notes/5.x.x.md | 6 ++++
.../configs/EVPN-MULTICAST-DISABLED.cfg | 6 ++--
.../configs/EVPN-MULTICAST-L3LEAF1A.cfg | 36 +++++++++----------
.../configs/EVPN-MULTICAST-L3LEAF1B.cfg | 36 +++++++++----------
.../configs/EVPN-MULTICAST-L3LEAF2A.cfg | 10 +++---
.../configs/EVPN-MULTICAST-L3LEAF3A.cfg | 10 +++---
.../configs/EVPN-MULTICAST-L3LEAF3B.cfg | 10 +++---
.../EVPN-MULTICAST-DISABLED.yml | 6 ++--
.../EVPN-MULTICAST-L3LEAF1A.yml | 18 +++++-----
.../EVPN-MULTICAST-L3LEAF1B.yml | 18 +++++-----
.../EVPN-MULTICAST-L3LEAF2A.yml | 10 +++---
.../EVPN-MULTICAST-L3LEAF3A.yml | 10 +++---
.../EVPN-MULTICAST-L3LEAF3B.yml | 10 +++---
.../group_vars/EVPN_MULTICAST_TESTS.yml | 2 ++
.../network-services-multicast-settings.md | 9 +++--
.../schema/eos_designs.schema.yml | 9 ++++-
.../defs_network_services.schema.yml | 7 +++-
.../shared_utils/filtered_tenants.py | 1 +
.../network_services/vxlan_interface.py | 29 ++++++++-------
.../pyavd/api/ip_addressing/__init__.py | 12 +++----
21 files changed, 161 insertions(+), 115 deletions(-)
diff --git a/ansible_collections/arista/avd/docs/porting-guides/5.x.x.md b/ansible_collections/arista/avd/docs/porting-guides/5.x.x.md
index c82ba9a0a6b..687c08d9ad6 100644
--- a/ansible_collections/arista/avd/docs/porting-guides/5.x.x.md
+++ b/ansible_collections/arista/avd/docs/porting-guides/5.x.x.md
@@ -108,6 +108,27 @@ tenants:
+ always_redistribute_igmp: true
```
+### `vrf_id` is used instead of `vrf_vni` to calculate the IP address of EVPN underlay multicast group for a VRF
+
+AVD versions below 5.0.0 uses `vrf_vni` in the algorithm to set the offset in order to get the IP address of EVPN underlay multicast group.
+
+In AVD version 5.0.0, `vrf_id` is used instead of `vrf_vni` to calculate the offset for IP address of EVPN underlay multicast group for a VRF when `evpn_l3_multicast` is enabled. The configurations will only change in case `vrf_id` and `vrf_vni` are set to different values or `evpn_l3_multicast.evpn_underlay_l3_multicast_group` is set under `vrfs`.
+
+To retain the previous configuration, set group ip directly under the `vrfs` using `evpn_l3_multicast.evpn_underlay_l3_multicast_group`
+
+```diff
+tenants:
+ - name: Tenant_C
+ evpn_l3_multicast:
+ enabled: true
+ vrfs:
+ - name: TEN_C_L3_MULTICAST_ENABLED_130_131
+ evpn_l3_multicast:
+ enabled: true
++ evpn_underlay_l3_multicast_group: 232.0.64.2
+ <...>
+```
+
### Base class for custom IP addressing `AvdIpAddressing` was moved
The `AvdIpAddressing` class was moved from the Ansible collection to `pyavd.api.ip_addressing`. Import statements must be updated.
diff --git a/ansible_collections/arista/avd/docs/release-notes/5.x.x.md b/ansible_collections/arista/avd/docs/release-notes/5.x.x.md
index 3716166b920..e66bab5714e 100644
--- a/ansible_collections/arista/avd/docs/release-notes/5.x.x.md
+++ b/ansible_collections/arista/avd/docs/release-notes/5.x.x.md
@@ -182,6 +182,12 @@ Starting AVD 5.0.0, by default `redistribute igmp` will only get configured when
See the [Porting guide for AVD 5.x.x](../porting-guides/5.x.x.md#removed-redistribute-igmp-from-bgp-vlan-config-belong-to-a-vrf-with-evpn-multicast)
+#### vrf_id will be used instead of vrf_vni to get the ip address of evpn underlay multicast group
+
+Starting AVD 5.0.0, `vrf_id` is used instead of `vrf_vni` in the algorithm to set the offset for IP address of EVPN underlay multicast group when `evpn_l3_multicast` is enabled.
+
+See the [Porting guide for AVD 5.x.x](../porting-guides/5.x.x.md#vrf_id-is-used-instead-of-vrf_vni-to-calculate-the-ip-address-of-evpn-underlay-multicast-group-for-a-vrf)
+
#### AvdInterfaceDescriptions breaking changes
The class `AvdInterfaceDescriptions` was moved to `pyavd` and heavily modified.
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-DISABLED.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-DISABLED.cfg
index de86dc172ac..b753170f95a 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-DISABLED.cfg
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-DISABLED.cfg
@@ -778,9 +778,9 @@ router bgp 65106
redistribute connected
!
vrf TEN_C_L3_MULTICAST_ENABLED_130_131
- rd 192.168.255.8:31
- route-target import evpn 31:31
- route-target export evpn 31:31
+ rd 192.168.255.8:66
+ route-target import evpn 66:66
+ route-target export evpn 66:66
router-id 192.168.255.8
redistribute connected
!
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF1A.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF1A.cfg
index ca58e3f305a..26b3aafb5af 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF1A.cfg
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF1A.cfg
@@ -192,10 +192,6 @@ vlan 3022
name MLAG_iBGP_MULTICAST_DISABLED_5_6
trunk group LEAF_PEER_L3
!
-vlan 3030
- name MLAG_iBGP_TEN_C_L3_MULTICAST_ENABLED_130_131
- trunk group LEAF_PEER_L3
-!
vlan 3031
name MLAG_iBGP_TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231
trunk group LEAF_PEER_L3
@@ -228,6 +224,10 @@ vlan 3059
name MLAG_iBGP_TEN_E_L3_MULTICAST_EVPN_PEG_RP_NODES
trunk group LEAF_PEER_L3
!
+vlan 3065
+ name MLAG_iBGP_TEN_C_L3_MULTICAST_ENABLED_130_131
+ trunk group LEAF_PEER_L3
+!
vlan 4092
name MULTICAST_ENABLED_4092
!
@@ -655,13 +655,6 @@ interface Vlan3022
vrf MULTICAST_DISABLED_5_6
ip address 10.255.251.0/31
!
-interface Vlan3030
- description MLAG_PEER_L3_iBGP: vrf TEN_C_L3_MULTICAST_ENABLED_130_131
- no shutdown
- mtu 9214
- vrf TEN_C_L3_MULTICAST_ENABLED_130_131
- ip address 10.255.251.0/31
-!
interface Vlan3031
description MLAG_PEER_L3_iBGP: vrf TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231
no shutdown
@@ -718,6 +711,13 @@ interface Vlan3059
vrf TEN_E_L3_MULTICAST_EVPN_PEG_RP_NODES
ip address 10.255.251.0/31
!
+interface Vlan3065
+ description MLAG_PEER_L3_iBGP: vrf TEN_C_L3_MULTICAST_ENABLED_130_131
+ no shutdown
+ mtu 9214
+ vrf TEN_C_L3_MULTICAST_ENABLED_130_131
+ ip address 10.255.251.0/31
+!
interface Vlan4093
description MLAG_PEER_L3_PEERING
no shutdown
@@ -802,9 +802,9 @@ interface Vxlan1
vxlan vlan 252 multicast group 232.0.0.251
vxlan vlan 257 multicast group 232.0.1.0
vxlan vlan 4092 multicast group 232.0.15.251
- vxlan vrf TEN_C_L3_MULTICAST_ENABLED_130_131 multicast group 232.0.32.31
+ vxlan vrf TEN_C_L3_MULTICAST_ENABLED_130_131 multicast group 232.0.32.66
vxlan vrf TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231 multicast group 232.0.32.32
- vxlan vrf TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141 multicast group 232.0.64.40
+ vxlan vrf TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141 multicast group 232.0.64.2
vxlan vrf TEN_E_L3_MULTICAST_ENABLED_PEG_OVERRIDE multicast group 232.0.96.54
vxlan vrf TEN_E_L3_MULTICAST_EVPN_PEG_RP_NODES multicast group 232.0.96.59
vxlan vrf TEN_E_L3_MULTICAST_TRANSIT multicast group 232.0.96.51
@@ -1005,8 +1005,8 @@ router bgp 65101
vlan 330-331
!
vlan-aware-bundle TEN_C_L3_MULTICAST_ENABLED_130_131
- rd 192.168.255.3:31
- route-target both 31:31
+ rd 192.168.255.3:66
+ route-target both 66:66
redistribute igmp
redistribute learned
vlan 130-131,136-137
@@ -1132,10 +1132,10 @@ router bgp 65101
redistribute connected
!
vrf TEN_C_L3_MULTICAST_ENABLED_130_131
- rd 192.168.255.3:31
+ rd 192.168.255.3:66
evpn multicast
- route-target import evpn 31:31
- route-target export evpn 31:31
+ route-target import evpn 66:66
+ route-target export evpn 66:66
router-id 192.168.255.3
update wait-install
neighbor 10.255.251.1 peer group MLAG-IPv4-UNDERLAY-PEER
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF1B.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF1B.cfg
index d1190e29b8a..86edbce0e1e 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF1B.cfg
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF1B.cfg
@@ -192,10 +192,6 @@ vlan 3022
name MLAG_iBGP_MULTICAST_DISABLED_5_6
trunk group LEAF_PEER_L3
!
-vlan 3030
- name MLAG_iBGP_TEN_C_L3_MULTICAST_ENABLED_130_131
- trunk group LEAF_PEER_L3
-!
vlan 3031
name MLAG_iBGP_TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231
trunk group LEAF_PEER_L3
@@ -228,6 +224,10 @@ vlan 3059
name MLAG_iBGP_TEN_E_L3_MULTICAST_EVPN_PEG_RP_NODES
trunk group LEAF_PEER_L3
!
+vlan 3065
+ name MLAG_iBGP_TEN_C_L3_MULTICAST_ENABLED_130_131
+ trunk group LEAF_PEER_L3
+!
vlan 4092
name MULTICAST_ENABLED_4092
!
@@ -655,13 +655,6 @@ interface Vlan3022
vrf MULTICAST_DISABLED_5_6
ip address 10.255.251.1/31
!
-interface Vlan3030
- description MLAG_PEER_L3_iBGP: vrf TEN_C_L3_MULTICAST_ENABLED_130_131
- no shutdown
- mtu 9214
- vrf TEN_C_L3_MULTICAST_ENABLED_130_131
- ip address 10.255.251.1/31
-!
interface Vlan3031
description MLAG_PEER_L3_iBGP: vrf TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231
no shutdown
@@ -718,6 +711,13 @@ interface Vlan3059
vrf TEN_E_L3_MULTICAST_EVPN_PEG_RP_NODES
ip address 10.255.251.1/31
!
+interface Vlan3065
+ description MLAG_PEER_L3_iBGP: vrf TEN_C_L3_MULTICAST_ENABLED_130_131
+ no shutdown
+ mtu 9214
+ vrf TEN_C_L3_MULTICAST_ENABLED_130_131
+ ip address 10.255.251.1/31
+!
interface Vlan4093
description MLAG_PEER_L3_PEERING
no shutdown
@@ -802,9 +802,9 @@ interface Vxlan1
vxlan vlan 252 multicast group 232.0.0.251
vxlan vlan 257 multicast group 232.0.1.0
vxlan vlan 4092 multicast group 232.0.15.251
- vxlan vrf TEN_C_L3_MULTICAST_ENABLED_130_131 multicast group 232.0.32.31
+ vxlan vrf TEN_C_L3_MULTICAST_ENABLED_130_131 multicast group 232.0.32.66
vxlan vrf TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231 multicast group 232.0.32.32
- vxlan vrf TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141 multicast group 232.0.64.40
+ vxlan vrf TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141 multicast group 232.0.64.2
vxlan vrf TEN_E_L3_MULTICAST_ENABLED_PEG_OVERRIDE multicast group 232.0.96.54
vxlan vrf TEN_E_L3_MULTICAST_EVPN_PEG_RP_NODES multicast group 232.0.96.59
vxlan vrf TEN_E_L3_MULTICAST_TRANSIT multicast group 232.0.96.51
@@ -1005,8 +1005,8 @@ router bgp 65101
vlan 330-331
!
vlan-aware-bundle TEN_C_L3_MULTICAST_ENABLED_130_131
- rd 192.168.255.4:31
- route-target both 31:31
+ rd 192.168.255.4:66
+ route-target both 66:66
redistribute igmp
redistribute learned
vlan 130-131,136-137
@@ -1132,10 +1132,10 @@ router bgp 65101
redistribute connected
!
vrf TEN_C_L3_MULTICAST_ENABLED_130_131
- rd 192.168.255.4:31
+ rd 192.168.255.4:66
evpn multicast
- route-target import evpn 31:31
- route-target export evpn 31:31
+ route-target import evpn 66:66
+ route-target export evpn 66:66
router-id 192.168.255.4
update wait-install
neighbor 10.255.251.0 peer group MLAG-IPv4-UNDERLAY-PEER
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF2A.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF2A.cfg
index d23f74993ac..c6b435caf11 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF2A.cfg
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF2A.cfg
@@ -547,9 +547,9 @@ interface Vxlan1
vxlan vlan 252 multicast group 232.0.0.251
vxlan vlan 257 multicast group 232.0.1.0
vxlan vlan 4092 multicast group 232.0.15.251
- vxlan vrf TEN_C_L3_MULTICAST_ENABLED_130_131 multicast group 232.0.32.31
+ vxlan vrf TEN_C_L3_MULTICAST_ENABLED_130_131 multicast group 232.0.32.66
vxlan vrf TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231 multicast group 232.0.32.32
- vxlan vrf TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141 multicast group 232.0.64.40
+ vxlan vrf TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141 multicast group 232.0.64.2
vxlan vrf TEN_E_L3_MULTICAST_ENABLED_PEG_OVERRIDE multicast group 232.0.96.54
vxlan vrf TEN_E_L3_MULTICAST_EVPN_PEG_RP_NODES multicast group 232.0.96.59
vxlan vrf TEN_E_L3_MULTICAST_TRANSIT multicast group 232.0.96.51
@@ -871,10 +871,10 @@ router bgp 65103
redistribute connected
!
vrf TEN_C_L3_MULTICAST_ENABLED_130_131
- rd 192.168.255.5:31
+ rd 192.168.255.5:66
evpn multicast
- route-target import evpn 31:31
- route-target export evpn 31:31
+ route-target import evpn 66:66
+ route-target export evpn 66:66
router-id 192.168.255.5
redistribute connected
!
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF3A.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF3A.cfg
index f2ae221850c..0a5de47bdfc 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF3A.cfg
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF3A.cfg
@@ -575,9 +575,9 @@ interface Vxlan1
vxlan vlan 252 multicast group 232.0.0.251
vxlan vlan 257 multicast group 232.0.1.0
vxlan vlan 4092 multicast group 232.0.15.251
- vxlan vrf TEN_C_L3_MULTICAST_ENABLED_130_131 multicast group 232.0.32.31
+ vxlan vrf TEN_C_L3_MULTICAST_ENABLED_130_131 multicast group 232.0.32.66
vxlan vrf TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231 multicast group 232.0.32.32
- vxlan vrf TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141 multicast group 232.0.64.40
+ vxlan vrf TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141 multicast group 232.0.64.2
vxlan vrf TEN_E_L3_MULTICAST_ENABLED_PEG_OVERRIDE multicast group 232.0.96.54
vxlan vrf TEN_E_L3_MULTICAST_EVPN_PEG_RP_NODES multicast group 232.0.96.59
vxlan vrf TEN_E_L3_MULTICAST_TRANSIT multicast group 232.0.96.51
@@ -899,10 +899,10 @@ router bgp 65104
redistribute connected
!
vrf TEN_C_L3_MULTICAST_ENABLED_130_131
- rd 192.168.255.6:31
+ rd 192.168.255.6:66
evpn multicast
- route-target import evpn 31:31
- route-target export evpn 31:31
+ route-target import evpn 66:66
+ route-target export evpn 66:66
router-id 192.168.255.6
redistribute connected
!
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF3B.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF3B.cfg
index f5361a8419b..d2bab40cee7 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF3B.cfg
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/EVPN-MULTICAST-L3LEAF3B.cfg
@@ -575,9 +575,9 @@ interface Vxlan1
vxlan vlan 252 multicast group 232.0.0.251
vxlan vlan 257 multicast group 232.0.1.0
vxlan vlan 4092 multicast group 232.0.15.251
- vxlan vrf TEN_C_L3_MULTICAST_ENABLED_130_131 multicast group 232.0.32.31
+ vxlan vrf TEN_C_L3_MULTICAST_ENABLED_130_131 multicast group 232.0.32.66
vxlan vrf TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231 multicast group 232.0.32.32
- vxlan vrf TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141 multicast group 232.0.64.40
+ vxlan vrf TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141 multicast group 232.0.64.2
vxlan vrf TEN_E_L3_MULTICAST_ENABLED_PEG_OVERRIDE multicast group 232.0.96.54
vxlan vrf TEN_E_L3_MULTICAST_EVPN_PEG_RP_NODES multicast group 232.0.96.59
vxlan vrf TEN_E_L3_MULTICAST_TRANSIT multicast group 232.0.96.51
@@ -899,10 +899,10 @@ router bgp 65105
redistribute connected
!
vrf TEN_C_L3_MULTICAST_ENABLED_130_131
- rd 192.168.255.7:31
+ rd 192.168.255.7:66
evpn multicast
- route-target import evpn 31:31
- route-target export evpn 31:31
+ route-target import evpn 66:66
+ route-target export evpn 66:66
router-id 192.168.255.7
redistribute connected
!
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-DISABLED.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-DISABLED.yml
index 16e3e8ce643..e3c412fcbba 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-DISABLED.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-DISABLED.yml
@@ -147,16 +147,16 @@ router_bgp:
redistribute_routes:
- source_protocol: connected
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
- rd: 192.168.255.8:31
+ rd: 192.168.255.8:66
route_targets:
import:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
export:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
router_id: 192.168.255.8
redistribute_routes:
- source_protocol: connected
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF1A.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF1A.yml
index e50003d27a6..7e00b2d2da9 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF1A.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF1A.yml
@@ -204,16 +204,16 @@ router_bgp:
updates:
wait_install: true
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
- rd: 192.168.255.3:31
+ rd: 192.168.255.3:66
route_targets:
import:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
export:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
evpn_multicast: true
router_id: 192.168.255.3
redistribute_routes:
@@ -497,10 +497,10 @@ router_bgp:
- learned
vlan: 330-331
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
- rd: 192.168.255.3:31
+ rd: 192.168.255.3:66
route_targets:
both:
- - '31:31'
+ - 66:66
redistribute_routes:
- learned
- igmp
@@ -818,7 +818,7 @@ vlans:
- id: 137
name: L3_L2_MULTICAST_ENABLED_137
tenant: Tenant_C
-- id: 3030
+- id: 3065
name: MLAG_iBGP_TEN_C_L3_MULTICAST_ENABLED_130_131
trunk_groups:
- LEAF_PEER_L3
@@ -1120,7 +1120,7 @@ vlan_interfaces:
sparse_mode: true
local_interface: Loopback31
vrf: TEN_C_L3_MULTICAST_ENABLED_130_131
-- name: Vlan3030
+- name: Vlan3065
tenant: Tenant_C
type: underlay_peering
shutdown: false
@@ -1661,7 +1661,7 @@ vxlan_interface:
vni: 33
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
vni: 31
- multicast_group: 232.0.32.31
+ multicast_group: 232.0.32.66
- name: TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231
vni: 32
multicast_group: 232.0.32.32
@@ -1669,7 +1669,7 @@ vxlan_interface:
vni: 42
- name: TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141
vni: 41
- multicast_group: 232.0.64.40
+ multicast_group: 232.0.64.2
- name: TEN_E_L3_MULTICAST_ENABLED_PEG_OVERRIDE
vni: 55
multicast_group: 232.0.96.54
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF1B.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF1B.yml
index 2606a58dd95..4fd255aaf21 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF1B.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF1B.yml
@@ -204,16 +204,16 @@ router_bgp:
updates:
wait_install: true
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
- rd: 192.168.255.4:31
+ rd: 192.168.255.4:66
route_targets:
import:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
export:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
evpn_multicast: true
router_id: 192.168.255.4
redistribute_routes:
@@ -497,10 +497,10 @@ router_bgp:
- learned
vlan: 330-331
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
- rd: 192.168.255.4:31
+ rd: 192.168.255.4:66
route_targets:
both:
- - '31:31'
+ - 66:66
redistribute_routes:
- learned
- igmp
@@ -818,7 +818,7 @@ vlans:
- id: 137
name: L3_L2_MULTICAST_ENABLED_137
tenant: Tenant_C
-- id: 3030
+- id: 3065
name: MLAG_iBGP_TEN_C_L3_MULTICAST_ENABLED_130_131
trunk_groups:
- LEAF_PEER_L3
@@ -1120,7 +1120,7 @@ vlan_interfaces:
sparse_mode: true
local_interface: Loopback31
vrf: TEN_C_L3_MULTICAST_ENABLED_130_131
-- name: Vlan3030
+- name: Vlan3065
tenant: Tenant_C
type: underlay_peering
shutdown: false
@@ -1661,7 +1661,7 @@ vxlan_interface:
vni: 33
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
vni: 31
- multicast_group: 232.0.32.31
+ multicast_group: 232.0.32.66
- name: TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231
vni: 32
multicast_group: 232.0.32.32
@@ -1669,7 +1669,7 @@ vxlan_interface:
vni: 42
- name: TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141
vni: 41
- multicast_group: 232.0.64.40
+ multicast_group: 232.0.64.2
- name: TEN_E_L3_MULTICAST_ENABLED_PEG_OVERRIDE
vni: 55
multicast_group: 232.0.96.54
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF2A.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF2A.yml
index db262a54144..5f7f823a06d 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF2A.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF2A.yml
@@ -148,16 +148,16 @@ router_bgp:
redistribute_routes:
- source_protocol: connected
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
- rd: 192.168.255.5:31
+ rd: 192.168.255.5:66
route_targets:
import:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
export:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
evpn_multicast: true
router_id: 192.168.255.5
redistribute_routes:
@@ -1323,7 +1323,7 @@ vxlan_interface:
vni: 33
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
vni: 31
- multicast_group: 232.0.32.31
+ multicast_group: 232.0.32.66
- name: TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231
vni: 32
multicast_group: 232.0.32.32
@@ -1331,7 +1331,7 @@ vxlan_interface:
vni: 42
- name: TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141
vni: 41
- multicast_group: 232.0.64.40
+ multicast_group: 232.0.64.2
- name: TEN_E_L3_MULTICAST_ENABLED_PEG_OVERRIDE
vni: 55
multicast_group: 232.0.96.54
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF3A.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF3A.yml
index 6bc9c595114..3200e834dab 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF3A.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF3A.yml
@@ -148,16 +148,16 @@ router_bgp:
redistribute_routes:
- source_protocol: connected
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
- rd: 192.168.255.6:31
+ rd: 192.168.255.6:66
route_targets:
import:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
export:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
evpn_multicast: true
router_id: 192.168.255.6
redistribute_routes:
@@ -1361,7 +1361,7 @@ vxlan_interface:
vni: 33
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
vni: 31
- multicast_group: 232.0.32.31
+ multicast_group: 232.0.32.66
- name: TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231
vni: 32
multicast_group: 232.0.32.32
@@ -1369,7 +1369,7 @@ vxlan_interface:
vni: 42
- name: TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141
vni: 41
- multicast_group: 232.0.64.40
+ multicast_group: 232.0.64.2
- name: TEN_E_L3_MULTICAST_ENABLED_PEG_OVERRIDE
vni: 55
multicast_group: 232.0.96.54
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF3B.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF3B.yml
index b0ceca3dda2..5cc94e24f80 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF3B.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/EVPN-MULTICAST-L3LEAF3B.yml
@@ -148,16 +148,16 @@ router_bgp:
redistribute_routes:
- source_protocol: connected
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
- rd: 192.168.255.7:31
+ rd: 192.168.255.7:66
route_targets:
import:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
export:
- address_family: evpn
route_targets:
- - '31:31'
+ - 66:66
evpn_multicast: true
router_id: 192.168.255.7
redistribute_routes:
@@ -1361,7 +1361,7 @@ vxlan_interface:
vni: 33
- name: TEN_C_L3_MULTICAST_ENABLED_130_131
vni: 31
- multicast_group: 232.0.32.31
+ multicast_group: 232.0.32.66
- name: TEN_C_L3_MULTICAST_ENABLED_230_DISABLED_231
vni: 32
multicast_group: 232.0.32.32
@@ -1369,7 +1369,7 @@ vxlan_interface:
vni: 42
- name: TEN_D_L3_MULTICAST_ENABLED_140_DISABLED_141
vni: 41
- multicast_group: 232.0.64.40
+ multicast_group: 232.0.64.2
- name: TEN_E_L3_MULTICAST_ENABLED_PEG_OVERRIDE
vni: 55
multicast_group: 232.0.96.54
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/EVPN_MULTICAST_TESTS.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/EVPN_MULTICAST_TESTS.yml
index cfffdcd350d..08625e1fdc2 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/EVPN_MULTICAST_TESTS.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/EVPN_MULTICAST_TESTS.yml
@@ -255,6 +255,7 @@ tenants:
loopback: 31
loopback_ip_range: 10.255.1.0/24
vrf_vni: 31
+ vrf_id: 66
svis:
# Expected results:
# - VRF L3_MULTICAST_ENABLED_130_131 enabled for Multicast:
@@ -374,6 +375,7 @@ tenants:
loopback_ip_range: 10.255.41.0/24
evpn_l3_multicast:
enabled: true
+ evpn_underlay_l3_multicast_group: 232.0.64.2
vrf_vni: 41
svis:
# Expected results:
diff --git a/ansible_collections/arista/avd/roles/eos_designs/docs/tables/network-services-multicast-settings.md b/ansible_collections/arista/avd/roles/eos_designs/docs/tables/network-services-multicast-settings.md
index 749a4034d3d..f4601df1a69 100644
--- a/ansible_collections/arista/avd/roles/eos_designs/docs/tables/network-services-multicast-settings.md
+++ b/ansible_collections/arista/avd/roles/eos_designs/docs/tables/network-services-multicast-settings.md
@@ -15,7 +15,7 @@
| [ underlay_l2_multicast_group_ipv4_pool_offset](## ".[].evpn_l2_multicast.underlay_l2_multicast_group_ipv4_pool_offset") | Integer | | | | |
| [ fast_leave](## ".[].evpn_l2_multicast.fast_leave") | Boolean | | | | Enable IGMP snooping fast-leave feature for all SVIs and l2vlans within the Tenant. |
| [ always_redistribute_igmp](## ".[].evpn_l2_multicast.always_redistribute_igmp") | Boolean | | | | Always configure `redistribute igmp` under BGP for all SVIs within the Tenant if `evpn_l2_multicast` is True.
By default `redistribute igmp` is only configured when `evpn_l2_multicast` is True and `evpn_l3_multicast` for the VRF is False.
Configuring `redistribute igmp` when both L2 and L3 EVPN Multicast is enabled will take up additional control-plane and data-plane resources,
but it is required to support forwarding of TTL=1 multicast traffic within the VLAN.
This can be overridden per SVI. |
- | [ evpn_l3_multicast](## ".[].evpn_l3_multicast") | Dictionary | | | | Enable L3 Multicast for all SVIs and l3vlans within Tenant.
- In the evpn-l3ls design type, this enables L3 EVPN Multicast (aka OISM)'.
- Multicast group binding for VRF is created only for Multicast traffic. BULL traffic will use ingress-replication.
- Configures binding between VXLAN, VLAN, and multicast group IPv4 address using the following formula:
< l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool > + < vrf_vni - 1 > + < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset >.
- The recommendation is to assign a /20 block within the 232.0.0.0/8 Source-Specific Multicast range.
- If enabled on an SVI using the anycast default gateway feature, a diagnostic loopback (see below) MUST be configured to source IGMP traffic.
- Enables `evpn multicast` on the router bgp VRF.
- When enabled on an SVI:
- If switch is part of an MLAG pair, enables "pim ipv4 sparse-mode" on the SVI.
- If switch is standalone or A-A MH, enables "ip igmp" on the SVI.
- If "ip address virtual" is configured, enables "pim ipv4 local-interface" and uses the diagnostic Loopback defined in the VRF
- Requires `evpn_multicast` to also be set to `true`.
|
+ | [ evpn_l3_multicast](## ".[].evpn_l3_multicast") | Dictionary | | | | Enable L3 Multicast for all SVIs and l3vlans within Tenant.
- In the evpn-l3ls design type, this enables L3 EVPN Multicast (aka OISM)'.
- Multicast group binding for VRF is created only for Multicast traffic. BULL traffic will use ingress-replication.
- Configures binding between VXLAN, VLAN, and multicast group IPv4 address using the following formula:
< l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool > + < vrf_id - 1 > + < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset >.
- The recommendation is to assign a /20 block within the 232.0.0.0/8 Source-Specific Multicast range.
- If enabled on an SVI using the anycast default gateway feature, a diagnostic loopback (see below) MUST be configured to source IGMP traffic.
- Enables `evpn multicast` on the router bgp VRF.
- When enabled on an SVI:
- If switch is part of an MLAG pair, enables "pim ipv4 sparse-mode" on the SVI.
- If switch is standalone or A-A MH, enables "ip igmp" on the SVI.
- If "ip address virtual" is configured, enables "pim ipv4 local-interface" and uses the diagnostic Loopback defined in the VRF
- Requires `evpn_multicast` to also be set to `true`.
|
| [ enabled](## ".[].evpn_l3_multicast.enabled") | Boolean | | | | |
| [ evpn_underlay_l3_multicast_group_ipv4_pool](## ".[].evpn_l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool") | String | Required | | | IPv4_address/Mask. |
| [ evpn_underlay_l3_multicast_group_ipv4_pool_offset](## ".[].evpn_l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset") | Integer | | | | |
@@ -39,6 +39,7 @@
| [ - name](## ".[].vrfs.[].name") | String | Required, Unique | | | |
| [ evpn_l3_multicast](## ".[].vrfs.[].evpn_l3_multicast") | Dictionary | | | | Explicitly enable or disable evpn_l3_multicast to override setting of `.[].evpn_l3_multicast.enabled`.
Allow override of `.[].evpn_l3_multicast` node_settings.
Requires `evpn_multicast` to also be set to `true`.
|
| [ enabled](## ".[].vrfs.[].evpn_l3_multicast.enabled") | Boolean | | | | |
+ | [ evpn_underlay_l3_multicast_group](## ".[].vrfs.[].evpn_l3_multicast.evpn_underlay_l3_multicast_group") | String | | | | IPv4 address of evpn underlay l3 multicast group.
To override multicast range set using the formula < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool > + < vrf_id - 1 > + < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset >. |
| [ evpn_peg](## ".[].vrfs.[].evpn_l3_multicast.evpn_peg") | List, items: Dictionary | | | | For each group of nodes, allow configuration of EVPN PEG features. |
| [ - nodes](## ".[].vrfs.[].evpn_l3_multicast.evpn_peg.[].nodes") | List, items: String | | | | Restrict configuration to specific nodes.
Will apply to all nodes with RP addresses configured if not set.
|
| [ - <str>](## ".[].vrfs.[].evpn_l3_multicast.evpn_peg.[].nodes.[]") | String | | | | |
@@ -154,7 +155,7 @@
# - In the evpn-l3ls design type, this enables L3 EVPN Multicast (aka OISM)'.
# - Multicast group binding for VRF is created only for Multicast traffic. BULL traffic will use ingress-replication.
# - Configures binding between VXLAN, VLAN, and multicast group IPv4 address using the following formula:
- # < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool > + < vrf_vni - 1 > + < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset >.
+ # < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool > + < vrf_id - 1 > + < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset >.
# - The recommendation is to assign a /20 block within the 232.0.0.0/8 Source-Specific Multicast range.
# - If enabled on an SVI using the anycast default gateway feature, a diagnostic loopback (see below) MUST be configured to source IGMP traffic.
# - Enables `evpn multicast` on the router bgp VRF.
@@ -232,6 +233,10 @@
evpn_l3_multicast:
enabled:
+ # IPv4 address of evpn underlay l3 multicast group.
+ # To override multicast range set using the formula < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool > + < vrf_id - 1 > + < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset >.
+ evpn_underlay_l3_multicast_group:
+
# For each group of nodes, allow configuration of EVPN PEG features.
evpn_peg:
diff --git a/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml b/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml
index 0f3f59fe782..6119aca588d 100644
--- a/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml
+++ b/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml
@@ -5816,7 +5816,7 @@ $defs:
Multicast group binding for VRF is created only for Multicast traffic.
BULL traffic will use ingress-replication.\n- Configures binding between
VXLAN, VLAN, and multicast group IPv4 address using the following formula:\n
- \ < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool > + < vrf_vni
+ \ < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool > + < vrf_id
- 1 > + < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset
>.\n- The recommendation is to assign a /20 block within the 232.0.0.0/8
Source-Specific Multicast range.\n- If enabled on an SVI using the anycast
@@ -6240,6 +6240,13 @@ $defs:
keys:
enabled:
type: bool
+ evpn_underlay_l3_multicast_group:
+ type: str
+ description: 'IPv4 address of evpn underlay l3 multicast group.
+
+ To override multicast range set using the formula < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool
+ > + < vrf_id - 1 > + < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset
+ >.'
evpn_peg:
type: list
description: For each group of nodes, allow configuration of EVPN
diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_network_services.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_network_services.schema.yml
index 62cde91efa8..ec12b299d3f 100644
--- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_network_services.schema.yml
+++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_network_services.schema.yml
@@ -187,7 +187,7 @@ $defs:
- In the evpn-l3ls design type, this enables L3 EVPN Multicast (aka OISM)'.
- Multicast group binding for VRF is created only for Multicast traffic. BULL traffic will use ingress-replication.
- Configures binding between VXLAN, VLAN, and multicast group IPv4 address using the following formula:
- < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool > + < vrf_vni - 1 > + < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset >.
+ < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool > + < vrf_id - 1 > + < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset >.
- The recommendation is to assign a /20 block within the 232.0.0.0/8 Source-Specific Multicast range.
- If enabled on an SVI using the anycast default gateway feature, a diagnostic loopback (see below) MUST be configured to source IGMP traffic.
- Enables `evpn multicast` on the router bgp VRF.
@@ -513,6 +513,11 @@ $defs:
keys:
enabled:
type: bool
+ evpn_underlay_l3_multicast_group:
+ type: str
+ description: |-
+ IPv4 address of evpn underlay l3 multicast group.
+ To override multicast range set using the formula < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool > + < vrf_id - 1 > + < l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset >.
evpn_peg:
type: list
description: For each group of nodes, allow configuration of EVPN PEG features.
diff --git a/python-avd/pyavd/_eos_designs/shared_utils/filtered_tenants.py b/python-avd/pyavd/_eos_designs/shared_utils/filtered_tenants.py
index c2c143a0d5d..79da5e83e4a 100644
--- a/python-avd/pyavd/_eos_designs/shared_utils/filtered_tenants.py
+++ b/python-avd/pyavd/_eos_designs/shared_utils/filtered_tenants.py
@@ -221,6 +221,7 @@ def filtered_vrfs(self: SharedUtils, tenant: dict) -> list[dict]:
evpn_l3_multicast_enabled = default(get(vrf, "evpn_l3_multicast.enabled"), get(tenant, "evpn_l3_multicast.enabled"))
if self.evpn_multicast:
vrf["_evpn_l3_multicast_enabled"] = evpn_l3_multicast_enabled
+ vrf["_evpn_l3_multicast_group_ip"] = get(vrf, "evpn_l3_multicast.evpn_underlay_l3_multicast_group")
rps = []
for rp_entry in default(get(vrf, "pim_rp_addresses"), get(tenant, "pim_rp_addresses"), []):
diff --git a/python-avd/pyavd/_eos_designs/structured_config/network_services/vxlan_interface.py b/python-avd/pyavd/_eos_designs/structured_config/network_services/vxlan_interface.py
index 3bad01cd7c6..6e931e88353 100644
--- a/python-avd/pyavd/_eos_designs/structured_config/network_services/vxlan_interface.py
+++ b/python-avd/pyavd/_eos_designs/structured_config/network_services/vxlan_interface.py
@@ -179,19 +179,22 @@ def _get_vxlan_interface_config_for_vrf(self: AvdStructuredConfigNetworkServices
vrf_data = {"name": vrf_name, "vni": vni}
if get(vrf, "_evpn_l3_multicast_enabled"):
- underlay_l3_multicast_group_ipv4_pool = get(
- tenant,
- "evpn_l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool",
- required=True,
- org_key=f"'evpn_l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool' for Tenant: {tenant['name']}",
- )
- underlay_l3_mcast_group_ipv4_pool_offset = get(tenant, "evpn_l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset", default=0)
- vrf_data["multicast_group"] = self.shared_utils.ip_addressing.evpn_underlay_l3_multicast_group(
- underlay_l3_multicast_group_ipv4_pool,
- vni,
- vrf_id,
- underlay_l3_mcast_group_ipv4_pool_offset,
- )
+ if vrf_multicast_group := get(vrf, "_evpn_l3_multicast_group_ip"):
+ vrf_data["multicast_group"] = vrf_multicast_group
+ else:
+ underlay_l3_multicast_group_ipv4_pool = get(
+ tenant,
+ "evpn_l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool",
+ required=True,
+ org_key=f"'evpn_l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool' for Tenant: {tenant['name']}",
+ )
+ underlay_l3_mcast_group_ipv4_pool_offset = get(tenant, "evpn_l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool_offset", default=0)
+ vrf_data["multicast_group"] = self.shared_utils.ip_addressing.evpn_underlay_l3_multicast_group(
+ underlay_l3_multicast_group_ipv4_pool,
+ vni,
+ vrf_id,
+ underlay_l3_mcast_group_ipv4_pool_offset,
+ )
# Duplicate check is not done on the actual list of vlans, but instead on our local "vnis" list.
# This is necessary to find duplicate VNIs across multiple object types.
diff --git a/python-avd/pyavd/api/ip_addressing/__init__.py b/python-avd/pyavd/api/ip_addressing/__init__.py
index b90127e3316..fd08dc88254 100644
--- a/python-avd/pyavd/api/ip_addressing/__init__.py
+++ b/python-avd/pyavd/api/ip_addressing/__init__.py
@@ -314,16 +314,12 @@ def vrf_loopback_ip(self, pool: str) -> str:
def evpn_underlay_l3_multicast_group(
self,
underlay_l3_multicast_group_ipv4_pool: str,
- vrf_vni: int,
- vrf_id: int, # pylint: disable=unused-argument # noqa: ARG002
+ vrf_vni: int, # pylint: disable=unused-argument # noqa: ARG002
+ vrf_id: int,
evpn_underlay_l3_multicast_group_ipv4_pool_offset: int,
) -> str:
- """
- Return IP address to be used for EVPN underlay L3 multicast group.
-
- TODO: Change algorithm to use VRF ID instead of VRF VNI as offset.
- """
- offset = vrf_vni - 1 + evpn_underlay_l3_multicast_group_ipv4_pool_offset
+ """Return IP address to be used for EVPN underlay L3 multicast group."""
+ offset = vrf_id - 1 + evpn_underlay_l3_multicast_group_ipv4_pool_offset
return get_ip_from_pool(underlay_l3_multicast_group_ipv4_pool, 32, offset, 0)
def evpn_underlay_l2_multicast_group(
From 5584778f4649d266e8e6d80a869b86fc46f87c82 Mon Sep 17 00:00:00 2001
From: Claus Holbech
Date: Wed, 18 Sep 2024 15:04:14 +0200
Subject: [PATCH 2/3] Fix(eos_designs): Fix schema validation of dynamic keys
(#4474)
---
.../avd/plugins/action/eos_designs_facts.py | 12 ++---
.../docs/tables/connected-endpoints.md | 47 +-----------------
.../docs/tables/custom-node-type-keys.key.md | 16 ++++++
.../eos_designs/docs/tables/type-setting.md | 4 +-
.../schema/eos_designs.schema.yml | 49 +++----------------
.../connected_endpoints.schema.yml | 39 ---------------
.../custom_node_type.schema.yml | 13 +++++
.../defs_connected_endpoints.schema.yml | 1 -
.../schema/schema_fragments/type.schema.yml | 4 +-
.../structured_config/__init__.py | 21 +++-----
python-avd/pyavd/_schema/avd_meta_schema.json | 7 ++-
python-avd/pyavd/_schema/avddataconverter.py | 5 +-
python-avd/pyavd/_schema/avdvalidator.py | 12 +++--
python-avd/pyavd/_schema/utils.py | 29 +++++++++++
python-avd/pyavd/_utils/get_all.py | 5 +-
python-avd/pyavd/validate_inputs.py | 9 ----
.../schema_tools/generate_docs/tablerowgen.py | 2 +-
.../schema_tools/generate_docs/yamllinegen.py | 2 +-
.../metaschema/meta_schema_model.py | 12 ++---
.../tests/pyavd/schema/test_avdschema_bool.py | 2 +-
.../tests/pyavd/schema/test_avdschema_int.py | 2 +-
.../tests/pyavd/schema/test_avdschema_str.py | 2 +-
.../artifacts/eos_designs.schema.yml | 3 +-
23 files changed, 116 insertions(+), 182 deletions(-)
create mode 100644 ansible_collections/arista/avd/roles/eos_designs/docs/tables/custom-node-type-keys.key.md
create mode 100644 python-avd/pyavd/_eos_designs/schema/schema_fragments/custom_node_type.schema.yml
create mode 100644 python-avd/pyavd/_schema/utils.py
diff --git a/ansible_collections/arista/avd/plugins/action/eos_designs_facts.py b/ansible_collections/arista/avd/plugins/action/eos_designs_facts.py
index 88e4bdbde85..f11bfc2f9e4 100644
--- a/ansible_collections/arista/avd/plugins/action/eos_designs_facts.py
+++ b/ansible_collections/arista/avd/plugins/action/eos_designs_facts.py
@@ -144,15 +144,6 @@ def create_avd_switch_facts_instances(self, fabric_hosts: list, hostvars: object
# Fetch all templated Ansible vars for this host
host_hostvars = dict(hostvars.get(host))
- # Initialize SharedUtils class to be passed to EosDesignsFacts below.
- shared_utils = SharedUtils(hostvars=host_hostvars, templar=self.templar, schema=avdschematools.avdschema)
-
- # Insert dynamic keys into the input data if not set.
- # These keys are required by the schema, but the default values are set inside shared_utils.
- host_hostvars.setdefault("node_type_keys", shared_utils.node_type_keys)
- host_hostvars.setdefault("connected_endpoints_keys", shared_utils.connected_endpoints_keys)
- host_hostvars.setdefault("network_services_keys", shared_utils.network_services_keys)
-
# Set correct hostname in schema tools and perform conversion and validation
avdschematools.hostname = host
host_result = avdschematools.convert_and_validate_data(host_hostvars, return_counters=True)
@@ -168,6 +159,9 @@ def create_avd_switch_facts_instances(self, fabric_hosts: list, hostvars: object
# This is used to access EosDesignsFacts objects of other switches during rendering of one switch.
host_hostvars["avd_switch_facts"] = avd_switch_facts
+ # Initialize SharedUtils class to be passed to EosDesignsFacts below.
+ shared_utils = SharedUtils(hostvars=host_hostvars, templar=self.templar, schema=avdschematools.avdschema)
+
# Create an instance of EosDesignsFacts and insert into common avd_switch_facts dict
avd_switch_facts[host] = {"switch": EosDesignsFacts(hostvars=host_hostvars, shared_utils=shared_utils)}
diff --git a/ansible_collections/arista/avd/roles/eos_designs/docs/tables/connected-endpoints.md b/ansible_collections/arista/avd/roles/eos_designs/docs/tables/connected-endpoints.md
index 00ce398cda7..586c49e88f6 100644
--- a/ansible_collections/arista/avd/roles/eos_designs/docs/tables/connected-endpoints.md
+++ b/ansible_collections/arista/avd/roles/eos_designs/docs/tables/connected-endpoints.md
@@ -7,7 +7,7 @@
| Variable | Type | Required | Default | Value Restrictions | Description |
| -------- | ---- | -------- | ------- | ------------------ | ----------- |
- | [<connected_endpoints_keys.key>](## "") | List, items: Dictionary | | See (+) on YAML tab | | This should be applied to group_vars or host_vars where endpoints are connecting.
`connected_endpoints_keys.key` is one of the keys under "connected_endpoints_keys".
The default keys are `servers`, `firewalls`, `routers`, `load_balancers`, and `storage_arrays`.
|
+ | [<connected_endpoints_keys.key>](## "") | List, items: Dictionary | | | | This should be applied to group_vars or host_vars where endpoints are connecting.
`connected_endpoints_keys.key` is one of the keys under "connected_endpoints_keys".
|
| [ - name](## ".[].name") | String | Required, Unique | | | Endpoint name will be used in the switchport description. |
| [ rack](## ".[].rack") | String | | | | Rack is used for documentation purposes only. |
| [ adapters](## ".[].adapters") | List, items: Dictionary | | | | A list of adapters, group by adapters leveraging the same port-profile. |
@@ -165,8 +165,7 @@
```yaml
# This should be applied to group_vars or host_vars where endpoints are connecting.
# `connected_endpoints_keys.key` is one of the keys under "connected_endpoints_keys".
- # The default keys are `servers`, `firewalls`, `routers`, `load_balancers`, and `storage_arrays`.
- : # (1)!
+ :
# Endpoint name will be used in the switchport description.
- name:
@@ -551,45 +550,3 @@
# Custom structured config added under ethernet_interfaces.[name=] for eos_cli_config_gen.
structured_config:
```
-
- 1. Default Value
-
- ```yaml
- :
- - description: Server
- key: servers
- type: server
- - description: Firewall
- key: firewalls
- type: firewall
- - description: Router
- key: routers
- type: router
- - description: Load Balancer
- key: load_balancers
- type: load_balancer
- - description: Storage Array
- key: storage_arrays
- type: storage_array
- - description: CPE
- key: cpes
- type: cpe
- - description: Workstation
- key: workstations
- type: workstation
- - description: Access Point
- key: access_points
- type: access_point
- - description: Phone
- key: phones
- type: phone
- - description: Printer
- key: printers
- type: printer
- - description: Camera
- key: cameras
- type: camera
- - description: Generic Device
- key: generic_devices
- type: generic_device
- ```
diff --git a/ansible_collections/arista/avd/roles/eos_designs/docs/tables/custom-node-type-keys.key.md b/ansible_collections/arista/avd/roles/eos_designs/docs/tables/custom-node-type-keys.key.md
new file mode 100644
index 00000000000..168389787e8
--- /dev/null
+++ b/ansible_collections/arista/avd/roles/eos_designs/docs/tables/custom-node-type-keys.key.md
@@ -0,0 +1,16 @@
+
+=== "Table"
+
+ | Variable | Type | Required | Default | Value Restrictions | Description |
+ | -------- | ---- | -------- | ------- | ------------------ | ----------- |
+ | [<custom_node_type_keys.key>](## "") | Dictionary | | | | |
+
+=== "YAML"
+
+ ```yaml
+ :
+ ```
diff --git a/ansible_collections/arista/avd/roles/eos_designs/docs/tables/type-setting.md b/ansible_collections/arista/avd/roles/eos_designs/docs/tables/type-setting.md
index ce8e58d01b8..f9c422de72e 100644
--- a/ansible_collections/arista/avd/roles/eos_designs/docs/tables/type-setting.md
+++ b/ansible_collections/arista/avd/roles/eos_designs/docs/tables/type-setting.md
@@ -7,12 +7,12 @@
| Variable | Type | Required | Default | Value Restrictions | Description |
| -------- | ---- | -------- | ------- | ------------------ | ----------- |
- | [type](## "type") | String | | | Valid Values:
-
| The `type:` variable needs to be defined for each device in the fabric.
This is leveraged to load the appropriate template to generate the configuration.
|
+ | [type](## "type") | String | | | Valid Values:
-
-
| The `type:` variable needs to be defined for each device in the fabric.
This is leveraged to load the appropriate template to generate the configuration.
|
=== "YAML"
```yaml
# The `type:` variable needs to be defined for each device in the fabric.
# This is leveraged to load the appropriate template to generate the configuration.
- type: ">
+ type: " | "">
```
diff --git a/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml b/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml
index 6119aca588d..35581fddd55 100644
--- a/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml
+++ b/python-avd/pyavd/_eos_designs/schema/eos_designs.schema.yml
@@ -3922,7 +3922,9 @@ keys:
documentation_options:
table: type-setting
type: str
- dynamic_valid_values: node_type_keys.type
+ dynamic_valid_values:
+ - custom_node_type_keys.type
+ - node_type_keys.type
description: 'The `type:` variable needs to be defined for each device in the
fabric.
@@ -4737,43 +4739,11 @@ dynamic_keys:
display_name: Connected Endpoints
documentation_options:
table: connected-endpoints
- default:
- - key: servers
- type: server
- description: Server
- - key: firewalls
- type: firewall
- description: Firewall
- - key: routers
- type: router
- description: Router
- - key: load_balancers
- type: load_balancer
- description: Load Balancer
- - key: storage_arrays
- type: storage_array
- description: Storage Array
- - key: cpes
- type: cpe
- description: CPE
- - key: workstations
- type: workstation
- description: Workstation
- - key: access_points
- type: access_point
- description: Access Point
- - key: phones
- type: phone
- description: Phone
- - key: printers
- type: printer
- description: Printer
- - key: cameras
- type: camera
- description: Camera
- - key: generic_devices
- type: generic_device
- description: Generic Device
+ custom_node_type_keys.key:
+ $ref: eos_designs#/$defs/node_type
+ type: dict
+ documentation_options:
+ hide_keys: true
network_services_keys.name:
$ref: eos_designs#/$defs/network_services
type: list
@@ -5447,9 +5417,6 @@ $defs:
`connected_endpoints_keys.key` is one of the keys under "connected_endpoints_keys".
- The default keys are `servers`, `firewalls`, `routers`, `load_balancers`, and
- `storage_arrays`.
-
'
items:
type: dict
diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/connected_endpoints.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/connected_endpoints.schema.yml
index 5165b7553da..8439934367c 100644
--- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/connected_endpoints.schema.yml
+++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/connected_endpoints.schema.yml
@@ -12,42 +12,3 @@ dynamic_keys:
display_name: Connected Endpoints
documentation_options:
table: connected-endpoints
- default:
- # NOTE: there is a static list of default endpoint keys in the
- # fabric connected endpoints documentation templates.
- - key: servers
- type: server
- description: Server
- - key: firewalls
- type: firewall
- description: Firewall
- - key: routers
- type: router
- description: Router
- - key: load_balancers
- type: load_balancer
- description: Load Balancer
- - key: storage_arrays
- type: storage_array
- description: Storage Array
- - key: cpes
- type: cpe
- description: CPE
- - key: workstations
- type: workstation
- description: Workstation
- - key: access_points
- type: access_point
- description: Access Point
- - key: phones
- type: phone
- description: Phone
- - key: printers
- type: printer
- description: Printer
- - key: cameras
- type: camera
- description: Camera
- - key: generic_devices
- type: generic_device
- description: Generic Device
diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/custom_node_type.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/custom_node_type.schema.yml
new file mode 100644
index 00000000000..129134c7030
--- /dev/null
+++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/custom_node_type.schema.yml
@@ -0,0 +1,13 @@
+# Copyright (c) 2023-2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+# yaml-language-server: $schema=../../../_schema/avd_meta_schema.json
+# Line above is used by RedHat's YAML Schema vscode extension
+# Use Ctrl + Space to get suggestions for every field. Autocomplete will pop up after typing 2 letters.
+type: dict
+dynamic_keys:
+ "custom_node_type_keys.key":
+ $ref: "eos_designs#/$defs/node_type"
+ type: dict
+ documentation_options:
+ hide_keys: true
diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_connected_endpoints.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_connected_endpoints.schema.yml
index d11ed699eff..20b56cbf481 100644
--- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_connected_endpoints.schema.yml
+++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/defs_connected_endpoints.schema.yml
@@ -12,7 +12,6 @@ $defs:
description: |
This should be applied to group_vars or host_vars where endpoints are connecting.
`connected_endpoints_keys.key` is one of the keys under "connected_endpoints_keys".
- The default keys are `servers`, `firewalls`, `routers`, `load_balancers`, and `storage_arrays`.
items:
type: dict
keys:
diff --git a/python-avd/pyavd/_eos_designs/schema/schema_fragments/type.schema.yml b/python-avd/pyavd/_eos_designs/schema/schema_fragments/type.schema.yml
index 137bac6937b..bb52e0fb44e 100644
--- a/python-avd/pyavd/_eos_designs/schema/schema_fragments/type.schema.yml
+++ b/python-avd/pyavd/_eos_designs/schema/schema_fragments/type.schema.yml
@@ -10,7 +10,9 @@ keys:
documentation_options:
table: type-setting
type: str
- dynamic_valid_values: "node_type_keys.type"
+ dynamic_valid_values:
+ - custom_node_type_keys.type
+ - node_type_keys.type
description: |
The `type:` variable needs to be defined for each device in the fabric.
This is leveraged to load the appropriate template to generate the configuration.
diff --git a/python-avd/pyavd/_eos_designs/structured_config/__init__.py b/python-avd/pyavd/_eos_designs/structured_config/__init__.py
index f804d5a88c5..c29309a4359 100644
--- a/python-avd/pyavd/_eos_designs/structured_config/__init__.py
+++ b/python-avd/pyavd/_eos_designs/structured_config/__init__.py
@@ -78,21 +78,6 @@ def get_structured_config(
Returns:
The structured_config as a dict
"""
- structured_config = {}
- module_vars = ChainMap(
- structured_config,
- vars,
- )
-
- # Initialize SharedUtils class to be passed to each python_module below.
- shared_utils = SharedUtils(hostvars=module_vars, templar=templar, schema=input_schema_tools.avdschema)
-
- # Insert dynamic keys into the input data if not set.
- # These keys are required by the schema, but the default values are set inside shared_utils.
- vars.setdefault("node_type_keys", shared_utils.node_type_keys)
- vars.setdefault("connected_endpoints_keys", shared_utils.connected_endpoints_keys)
- vars.setdefault("network_services_keys", shared_utils.network_services_keys)
-
# Validate input data
if validate:
result.update(input_schema_tools.convert_and_validate_data(vars))
@@ -100,6 +85,12 @@ def get_structured_config(
# Input data validation failed so return empty dict. Calling function should check result.get("failed").
return {}
+ structured_config = {}
+ module_vars = ChainMap(structured_config, vars)
+
+ # Initialize SharedUtils class to be passed to each python_module below.
+ shared_utils = SharedUtils(hostvars=module_vars, templar=templar, schema=input_schema_tools.avdschema)
+
for cls in AVD_STRUCTURED_CONFIG_CLASSES:
eos_designs_module: AvdFacts = cls(module_vars, shared_utils)
results = eos_designs_module.render()
diff --git a/python-avd/pyavd/_schema/avd_meta_schema.json b/python-avd/pyavd/_schema/avd_meta_schema.json
index 2ec84057ecf..bc97a16186d 100644
--- a/python-avd/pyavd/_schema/avd_meta_schema.json
+++ b/python-avd/pyavd/_schema/avd_meta_schema.json
@@ -376,8 +376,11 @@
"description": "Key is required"
},
"dynamic_valid_values": {
- "type": "string",
- "description": "Path to variable under the parent dictionary containing valid values.\nVariable path use dot-notation and variable path must be relative to the parent dictionary.\nIf an element of the variable path is a list, every list item will be unpacked.\nNote that this is building the schema from values in the _data_ being validated!"
+ "type": "array",
+ "items": {
+ "type": "string",
+ "description": "Path to variable under the parent dictionary containing valid values.\nVariable path use dot-notation and variable path must be relative to the parent dictionary.\nIf an element of the variable path is a list, every list item will be unpacked.\nNote that this is building the schema from values in the _data_ being validated!"
+ }
},
"$ref": {
"type": "string",
diff --git a/python-avd/pyavd/_schema/avddataconverter.py b/python-avd/pyavd/_schema/avddataconverter.py
index c3adeef9a8b..1410361f649 100644
--- a/python-avd/pyavd/_schema/avddataconverter.py
+++ b/python-avd/pyavd/_schema/avddataconverter.py
@@ -8,6 +8,8 @@
from pyavd._errors import AvdDeprecationWarning
from pyavd._utils import get_all
+from .utils import get_instance_with_defaults
+
SCHEMA_TO_PY_TYPE_MAP = {
"str": str,
"int": int,
@@ -92,7 +94,8 @@ def convert_dynamic_keys(self, dynamic_keys: dict, data: dict, schema: dict, pat
# Resolve "keys" from schema "dynamic_keys" by looking for the dynamic key in data.
keys = {}
for dynamic_key, childschema in dynamic_keys.items():
- resolved_keys = get_all(data, dynamic_key)
+ data_with_defaults = get_instance_with_defaults(data, dynamic_key, schema)
+ resolved_keys = get_all(data_with_defaults, dynamic_key)
for resolved_key in resolved_keys:
keys.setdefault(resolved_key, childschema)
diff --git a/python-avd/pyavd/_schema/avdvalidator.py b/python-avd/pyavd/_schema/avdvalidator.py
index bb984995caa..8456fcaccb7 100644
--- a/python-avd/pyavd/_schema/avdvalidator.py
+++ b/python-avd/pyavd/_schema/avdvalidator.py
@@ -9,6 +9,8 @@
from pyavd._errors import AvdValidationError
from pyavd._utils import get_all, get_all_with_path, get_indices_of_duplicate_items
+from .utils import get_instance_with_defaults
+
class AvdValidator:
def __init__(self, schema: dict) -> None:
@@ -109,7 +111,8 @@ def keys_validator(self, keys: dict, instance: dict, schema: dict, path: list[st
schema_dynamic_keys = schema.get("dynamic_keys", {})
dynamic_keys = {}
for dynamic_key, childschema in schema_dynamic_keys.items():
- resolved_keys = get_all(instance, dynamic_key)
+ instance_with_defaults = get_instance_with_defaults(instance, dynamic_key, schema)
+ resolved_keys = get_all(instance_with_defaults, dynamic_key)
for resolved_key in resolved_keys:
dynamic_keys.setdefault(resolved_key, childschema)
@@ -124,7 +127,8 @@ def keys_validator(self, keys: dict, instance: dict, schema: dict, path: list[st
# Run over child keys and check for required and update child schema with dynamic valid values before
# descending into validation of child schema.
- for key, childschema in all_keys.items():
+ for key in all_keys:
+ childschema = all_keys[key].copy()
if instance.get(key) is None:
# Validation of "required" on child keys
if childschema.get("required"):
@@ -135,7 +139,9 @@ def keys_validator(self, keys: dict, instance: dict, schema: dict, path: list[st
# Expand "dynamic_valid_values" in child schema and add to "valid_values"
if "dynamic_valid_values" in childschema:
- childschema.setdefault("valid_values", []).extend(get_all(instance, childschema["dynamic_valid_values"]))
+ for dynamic_valid_value in childschema["dynamic_valid_values"]:
+ instance_with_defaults = get_instance_with_defaults(instance, dynamic_valid_value, schema)
+ childschema.setdefault("valid_values", []).extend(get_all(instance_with_defaults, dynamic_valid_value))
# Perform regular validation of the child schema.
yield from self.validate(
diff --git a/python-avd/pyavd/_schema/utils.py b/python-avd/pyavd/_schema/utils.py
new file mode 100644
index 00000000000..418296d3b45
--- /dev/null
+++ b/python-avd/pyavd/_schema/utils.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+from collections import ChainMap
+
+from pyavd._utils import get
+
+
+def get_instance_with_defaults(instance: dict, dynamic_key_path: str, schema: dict) -> dict | ChainMap:
+ """
+ Returns the instance including any default value of the given dynamic key path.
+
+ If the dynamic key path is already in the instance, the instance is returned as-is.
+ """
+ dynamic_root_key = dynamic_key_path.split(".", maxsplit=1)[0]
+
+ if dynamic_root_key in instance:
+ # The key is already set, so no need to find the default value.
+ return instance
+
+ if dynamic_root_key == "node_type_keys":
+ # TODO: AVD6.0.0 remove this if block when we remove the reliance on design.type.
+ from pyavd._eos_designs.shared_utils.node_type_keys import DEFAULT_NODE_TYPE_KEYS
+
+ design_type = get(instance, "design.type", default="l3ls-evpn")
+ return ChainMap(instance, {"node_type_keys": DEFAULT_NODE_TYPE_KEYS[design_type]})
+
+ # Fetch default value from schema
+ return ChainMap(instance, {dynamic_root_key: get(schema, f"keys.{dynamic_root_key}.default")})
diff --git a/python-avd/pyavd/_utils/get_all.py b/python-avd/pyavd/_utils/get_all.py
index 3cc85dd7d55..ec9eb438359 100644
--- a/python-avd/pyavd/_utils/get_all.py
+++ b/python-avd/pyavd/_utils/get_all.py
@@ -3,6 +3,7 @@
# that can be found in the LICENSE file.
from __future__ import annotations
+from collections import ChainMap
from typing import TYPE_CHECKING, Any
from pyavd._errors import AristaAvdMissingVariableError
@@ -50,7 +51,7 @@ def get_all(data: Any, path: str, required: bool = False, org_path: str | None =
return output
- if isinstance(data, dict):
+ if isinstance(data, (dict, ChainMap)):
value = data.get(path_elements[0])
if value is None:
@@ -96,7 +97,7 @@ def get_all_with_path(data: Any, path: str, _current_path: list[str | int] | Non
for index, data_item in enumerate(data):
yield from get_all_with_path(data_item, path, _current_path=[*_current_path, index])
- elif isinstance(data, dict):
+ elif isinstance(data, (dict, ChainMap)):
value = data.get(path_elements[0])
if value is None:
diff --git a/python-avd/pyavd/validate_inputs.py b/python-avd/pyavd/validate_inputs.py
index 783437df50c..0c8bf3ba14a 100644
--- a/python-avd/pyavd/validate_inputs.py
+++ b/python-avd/pyavd/validate_inputs.py
@@ -22,20 +22,11 @@ def validate_inputs(inputs: dict) -> ValidationResult:
Validation result object with any validation errors or deprecation warnings.
"""
# pylint: disable=import-outside-toplevel
- from ._eos_designs.shared_utils import SharedUtils
from .avd_schema_tools import EosDesignsAvdSchemaTools
# pylint: enable=import-outside-toplevel
eos_designs_schema_tools = EosDesignsAvdSchemaTools()
- # Initialize SharedUtils class to fetch default variables below.
- shared_utils = SharedUtils(hostvars=inputs, templar=None, schema=eos_designs_schema_tools.avdschema)
-
- # Insert dynamic keys into the input data if not set.
- # These keys are required by the schema, but the default values are set inside shared_utils.
- inputs.setdefault("node_type_keys", shared_utils.node_type_keys)
- inputs.setdefault("connected_endpoints_keys", shared_utils.connected_endpoints_keys)
- inputs.setdefault("network_services_keys", shared_utils.network_services_keys)
# Inplace conversion of data
deprecation_warnings = eos_designs_schema_tools.convert_data(inputs)
diff --git a/python-avd/schema_tools/generate_docs/tablerowgen.py b/python-avd/schema_tools/generate_docs/tablerowgen.py
index a94df9d4a9a..c43c0c02f6a 100644
--- a/python-avd/schema_tools/generate_docs/tablerowgen.py
+++ b/python-avd/schema_tools/generate_docs/tablerowgen.py
@@ -210,7 +210,7 @@ def get_restrictions(self) -> list:
restrictions = []
valid_values = []
if getattr(self.schema, "dynamic_valid_values", None):
- valid_values.append(f"")
+ valid_values.extend(f"" for dynamic_valid_value in self.schema.dynamic_valid_values)
if getattr(self.schema, "valid_values", None):
valid_values.extend(self.schema.valid_values)
diff --git a/python-avd/schema_tools/generate_docs/yamllinegen.py b/python-avd/schema_tools/generate_docs/yamllinegen.py
index affe12ff446..45d3c7f2b4a 100644
--- a/python-avd/schema_tools/generate_docs/yamllinegen.py
+++ b/python-avd/schema_tools/generate_docs/yamllinegen.py
@@ -217,7 +217,7 @@ def get_restrictions(self) -> list:
restrictions = []
valid_values = []
if getattr(self.schema, "dynamic_valid_values", None):
- valid_values.append(f"")
+ valid_values.extend(f"" for dynamic_valid_value in self.schema.dynamic_valid_values)
if getattr(self.schema, "valid_values", None):
valid_values.extend(self.schema.valid_values)
diff --git a/python-avd/schema_tools/metaschema/meta_schema_model.py b/python-avd/schema_tools/metaschema/meta_schema_model.py
index 8fd52221adf..8f625c2426d 100644
--- a/python-avd/schema_tools/metaschema/meta_schema_model.py
+++ b/python-avd/schema_tools/metaschema/meta_schema_model.py
@@ -242,9 +242,9 @@ class ConvertType(str, Enum):
"""Maximum value"""
valid_values: list[int] | None = None
"""List of valid values"""
- dynamic_valid_values: str | None = None
+ dynamic_valid_values: list[str] | None = None
"""
- Path to variable under the parent dictionary containing valid values.
+ List of path to variable under the parent dictionary containing valid values.
Variable path use dot-notation and variable path must be relative to the parent dictionary.
If an element of the variable path is a list, every list item will be unpacked.
Note that this is building the schema from values in the _data_ being validated!
@@ -277,9 +277,9 @@ class ConvertType(str, Enum):
"""Default value"""
valid_values: list[bool] | None = None
"""List of valid values"""
- dynamic_valid_values: str | None = None
+ dynamic_valid_values: list[str] | None = None
"""
- Path to variable under the parent dictionary containing valid values.
+ List of paths to variable under the parent dictionary containing valid values.
Variable path use dot-notation and variable path must be relative to the parent dictionary.
If an element of the variable path is a list, every list item will be unpacked.
Note that this is building the schema from values in the _data_ being validated!
@@ -339,9 +339,9 @@ def __str__(self) -> str:
"""
valid_values: list[str] | None = None
"""List of valid values"""
- dynamic_valid_values: str | None = None
+ dynamic_valid_values: list[str] | None = None
"""
- Path to variable under the parent dictionary containing valid values.
+ List of paths to variable under the parent dictionary containing valid values.
Variable path use dot-notation and variable path must be relative to the parent dictionary.
If an element of the variable path is a list, every list item will be unpacked.
Note that this is building the schema from values in the _data_ being validated!
diff --git a/python-avd/tests/pyavd/schema/test_avdschema_bool.py b/python-avd/tests/pyavd/schema/test_avdschema_bool.py
index 486969362b8..73813ec4d19 100644
--- a/python-avd/tests/pyavd/schema/test_avdschema_bool.py
+++ b/python-avd/tests/pyavd/schema/test_avdschema_bool.py
@@ -23,7 +23,7 @@
"convert_types": ["int", "str"], # Part of meta schema but not implemented in converter
"default": True,
"valid_values": [True],
- "dynamic_valid_values": "valid_booleans", # Part of meta schema but not implemented in converter
+ "dynamic_valid_values": ["valid_booleans"], # Part of meta schema but not implemented in converter
"required": True,
"description": "Some boolean",
"display_name": "Boolean",
diff --git a/python-avd/tests/pyavd/schema/test_avdschema_int.py b/python-avd/tests/pyavd/schema/test_avdschema_int.py
index b0a047177de..61caacd0dbc 100644
--- a/python-avd/tests/pyavd/schema/test_avdschema_int.py
+++ b/python-avd/tests/pyavd/schema/test_avdschema_int.py
@@ -26,7 +26,7 @@
"min": 2,
"max": 20,
"valid_values": [0, 11, 22],
- "dynamic_valid_values": "valid_values", # Part of meta schema but not implemented in converter
+ "dynamic_valid_values": ["valid_values"], # Part of meta schema but not implemented in converter
"required": True,
"description": "Some integer",
"display_name": "Integer",
diff --git a/python-avd/tests/pyavd/schema/test_avdschema_str.py b/python-avd/tests/pyavd/schema/test_avdschema_str.py
index 470c6415d66..fcbf94a3e6d 100644
--- a/python-avd/tests/pyavd/schema/test_avdschema_str.py
+++ b/python-avd/tests/pyavd/schema/test_avdschema_str.py
@@ -29,7 +29,7 @@
"convert_to_lower_case": True,
"max_length": 4,
"min_length": 2,
- "dynamic_valid_values": "valid_strings", # Part of meta schema but not implemented in converter
+ "dynamic_valid_values": ["valid_strings"], # Part of meta schema but not implemented in converter
"pattern": "[abf14t].*",
"required": True,
"description": "Some string",
diff --git a/python-avd/tests/schema_tools/artifacts/eos_designs.schema.yml b/python-avd/tests/schema_tools/artifacts/eos_designs.schema.yml
index 6ef9fc56187..574120fcab8 100644
--- a/python-avd/tests/schema_tools/artifacts/eos_designs.schema.yml
+++ b/python-avd/tests/schema_tools/artifacts/eos_designs.schema.yml
@@ -2639,7 +2639,8 @@ keys:
documentation_options:
table: type-setting
type: str
- dynamic_valid_values: node_type_keys.type
+ dynamic_valid_values:
+ - node_type_keys.type
description: 'The `type:` variable needs to be defined for each device in the
fabric.
From 8cd95b8e7fbda4b681fa438622dae3f58a71f248 Mon Sep 17 00:00:00 2001
From: Guillaume Mulocher
Date: Wed, 18 Sep 2024 15:58:15 +0200
Subject: [PATCH 3/3] Fix(eos_designs): Use CP-Profile for WAN HA when
DP-Profile is not configured (#4309)
---
.../intended/configs/cv-pathfinder-edge2B.cfg | 21 +++---------------
.../cv-pathfinder-edge2B.yml | 22 +++----------------
.../host_vars/cv-pathfinder-edge1.yml | 4 ----
.../host_vars/cv-pathfinder-edge2B.yml | 8 +++++++
.../structured_config/overlay/ip_security.py | 11 ++++------
.../overlay/router_path_selection.py | 10 ++++++---
6 files changed, 25 insertions(+), 51 deletions(-)
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge2B.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge2B.cfg
index b5f96821144..a8d62ed9a0e 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge2B.cfg
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge2B.cfg
@@ -119,7 +119,7 @@ router path-selection
tcp mss ceiling ipv4 ingress
!
path-group CUSTOM_LAN_HA id 65535
- ipsec profile DP-PROFILE
+ ipsec profile ONE-PROFILE-TO-CONTROL-THEM-ALL
flow assignment lan
!
local interface Ethernet52
@@ -200,18 +200,11 @@ ip security
ike policy CP-IKE-POLICY
local-id 192.168.142.3
!
- ike policy DP-IKE-POLICY
- local-id 192.168.142.3
- !
sa policy CP-SA-POLICY
esp encryption aes256gcm128
pfs dh-group 14
!
- sa policy DP-SA-POLICY
- esp encryption aes256gcm128
- pfs dh-group 14
- !
- profile CP-PROFILE
+ profile ONE-PROFILE-TO-CONTROL-THEM-ALL
ike-policy CP-IKE-POLICY
sa-policy CP-SA-POLICY
connection start
@@ -219,16 +212,8 @@ ip security
dpd 10 50 clear
mode transport
!
- profile DP-PROFILE
- ike-policy DP-IKE-POLICY
- sa-policy DP-SA-POLICY
- connection start
- shared-key 7 ABCDEF1234567890666
- dpd 10 50 clear
- mode transport
- !
key controller
- profile DP-PROFILE
+ profile ONE-PROFILE-TO-CONTROL-THEM-ALL
!
interface Dps1
description DPS Interface
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge2B.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge2B.yml
index 7c33a128473..275fd08195b 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge2B.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge2B.yml
@@ -420,31 +420,15 @@ ip_extcommunity_lists:
extcommunities: soo 192.168.42.2:423
ip_security:
ike_policies:
- - name: DP-IKE-POLICY
- local_id: 192.168.142.3
- name: CP-IKE-POLICY
local_id: 192.168.142.3
sa_policies:
- - name: DP-SA-POLICY
- esp:
- encryption: aes256gcm128
- pfs_dh_group: 14
- name: CP-SA-POLICY
esp:
encryption: aes256gcm128
pfs_dh_group: 14
profiles:
- - name: DP-PROFILE
- ike_policy: DP-IKE-POLICY
- sa_policy: DP-SA-POLICY
- connection: start
- shared_key: ABCDEF1234567890666
- dpd:
- interval: 10
- time: 50
- action: clear
- mode: transport
- - name: CP-PROFILE
+ - name: ONE-PROFILE-TO-CONTROL-THEM-ALL
ike_policy: CP-IKE-POLICY
sa_policy: CP-SA-POLICY
connection: start
@@ -455,7 +439,7 @@ ip_security:
action: clear
mode: transport
key_controller:
- profile: DP-PROFILE
+ profile: ONE-PROFILE-TO-CONTROL-THEM-ALL
management_security:
ssl_profiles:
- name: profileA
@@ -595,7 +579,7 @@ router_path_selection:
ipv4_addresses:
- 172.17.0.5
- 172.17.0.7
- ipsec_profile: DP-PROFILE
+ ipsec_profile: ONE-PROFILE-TO-CONTROL-THEM-ALL
load_balance_policies:
- name: LB-DEFAULT-AVT-POLICY-CONTROL-PLANE
path_groups:
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge1.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge1.yml
index c1632b7fceb..85aa8d95d2c 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge1.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge1.yml
@@ -3,10 +3,6 @@
# Make sure to set the cv_token var on the molecule command line like:
# molecule converge -s eos_designs_unit_tests -- --limit cv-pathfinder-edge1 -e cv_token=$CV_TOKEN -v
-# serial_number: mockZscaler
-# cv_server: "www.cv-play.corp.arista.io"
-# zscaler_endpoints: null
-
# Testing multiple pathinfders on one device
wan_route_servers:
- hostname: cv-pathfinder-pathfinder1
diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge2B.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge2B.yml
index 9c0c6e4e16b..42d87a1f67c 100644
--- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge2B.yml
+++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge2B.yml
@@ -4,3 +4,11 @@
wan_ha:
lan_ha_path_group_name: CUSTOM_LAN_HA
+
+# Testing having only control_plane ipsec profile and making sure it is used for
+# HA path-group. Yes it makes for asymmetric config with 2A but this is a unit
+# test.
+wan_ipsec_profiles:
+ control_plane:
+ profile_name: ONE-PROFILE-TO-CONTROL-THEM-ALL
+ shared_key: ABCDEF1234567890
diff --git a/python-avd/pyavd/_eos_designs/structured_config/overlay/ip_security.py b/python-avd/pyavd/_eos_designs/structured_config/overlay/ip_security.py
index 7b6584d31ae..31e289b7669 100644
--- a/python-avd/pyavd/_eos_designs/structured_config/overlay/ip_security.py
+++ b/python-avd/pyavd/_eos_designs/structured_config/overlay/ip_security.py
@@ -47,7 +47,7 @@ def ip_security(self: AvdStructuredConfigOverlay) -> dict | None:
return strip_null_from_data(ip_security)
def _append_data_plane(self: AvdStructuredConfigOverlay, ip_security: dict, data_plane_config: dict) -> None:
- """In place update of ip_security."""
+ """In place update of ip_security for DataPlane."""
ike_policy_name = get(data_plane_config, "ike_policy_name", default="DP-IKE-POLICY") if self.shared_utils.wan_ha_ipsec else None
sa_policy_name = get(data_plane_config, "sa_policy_name", default="DP-SA-POLICY")
profile_name = get(data_plane_config, "profile_name", default="DP-PROFILE")
@@ -66,7 +66,7 @@ def _append_control_plane(self: AvdStructuredConfigOverlay, ip_security: dict, c
"""
In place update of ip_security for control plane data.
- expected to be called AFTER _append_data_plane
+ expected to be called AFTER _append_data_plane as CP is used for data-plane as well if not configured.
"""
ike_policy_name = get(control_plane_config, "ike_policy_name", default="CP-IKE-POLICY")
sa_policy_name = get(control_plane_config, "sa_policy_name", default="CP-SA-POLICY")
@@ -78,7 +78,7 @@ def _append_control_plane(self: AvdStructuredConfigOverlay, ip_security: dict, c
ip_security["profiles"].append(self._profile(profile_name, ike_policy_name, sa_policy_name, key))
if not ip_security.get("key_controller"):
- # If there is not data plane IPSec profile, use the control plane one for key controller
+ # If there is no data plane IPSec profile, use the control plane one for key controller
ip_security["key_controller"] = self._key_controller(profile_name)
def _ike_policy(self: AvdStructuredConfigOverlay, name: str) -> dict | None:
@@ -126,7 +126,4 @@ def _profile(self: AvdStructuredConfigOverlay, profile_name: str, ike_policy_nam
def _key_controller(self: AvdStructuredConfigOverlay, profile_name: str) -> dict | None:
"""Return a key_controller structure if the device is not a RR or pathfinder."""
- if self.shared_utils.is_wan_server:
- return None
-
- return {"profile": profile_name}
+ return None if self.shared_utils.is_wan_server else {"profile": profile_name}
diff --git a/python-avd/pyavd/_eos_designs/structured_config/overlay/router_path_selection.py b/python-avd/pyavd/_eos_designs/structured_config/overlay/router_path_selection.py
index 9756432525d..285169fa170 100644
--- a/python-avd/pyavd/_eos_designs/structured_config/overlay/router_path_selection.py
+++ b/python-avd/pyavd/_eos_designs/structured_config/overlay/router_path_selection.py
@@ -45,9 +45,13 @@ def _cp_ipsec_profile_name(self: AvdStructuredConfigOverlay) -> str:
@cached_property
def _dp_ipsec_profile_name(self: AvdStructuredConfigOverlay) -> str:
- """Returns the IPsec profile name to use for Data-Plane."""
- # TODO: need to use CP one if 'wan_ipsec_profiles.data_plane' not present
- return get(self._hostvars, "wan_ipsec_profiles.data_plane.profile_name", default="DP-PROFILE")
+ """Returns the IPsec profile name to use for Data-Plane.
+
+ If no data-plane config is present for IPsec, default to _cp_ipsec_profile_name
+ """
+ if (data_plane := get(self._hostvars, "wan_ipsec_profiles.data_plane")) is not None:
+ return get(data_plane, "profile_name", default="DP-PROFILE")
+ return self._cp_ipsec_profile_name
def _get_path_groups(self: AvdStructuredConfigOverlay) -> list:
"""Generate the required path-groups locally."""