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."""