diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/converge.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/converge.yml index 6cf330937d4..6e7ece65ad7 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/converge.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/converge.yml @@ -208,6 +208,27 @@ - ansible_failed_result is defined - ansible_failed_result.msg == expected_error_message +- name: Converge Negative tests for 'eos_designs_facts' + hosts: FAILURE_CONNECTED_ENDPOINT_PORT_PROFILE_DOES_NOT_EXIST_IN_FACTS + connection: local + tasks: + - name: Run failure scenario Test + block: + - name: Trigger Error + ansible.builtin.import_role: + name: arista.avd.eos_designs + rescue: + - name: Error message + run_once: true + ansible.builtin.debug: + var: ansible_failed_result.msg + - name: Assert eos_designs failed with the expected error message + run_once: true + ansible.builtin.assert: + that: + - ansible_failed_result is defined + - ansible_failed_result.msg == expected_error_message + - name: Converge Negative tests for 'eos_designs_structured_config' hosts: EOS_DESIGNS_FAILURES_INCLUDED gather_facts: false diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_DOWNLINK_POOLS_DUPLICATE.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_DOWNLINK_POOLS_DUPLICATE.yml index 344898d1504..be47432a690 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_DOWNLINK_POOLS_DUPLICATE.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_DOWNLINK_POOLS_DUPLICATE.yml @@ -34,5 +34,5 @@ l3leaf: uplink_switch_interfaces: [Ethernet3] expected_error_message: >- - Unexpected failure during module execution: Unable to assign IPs for uplinks. 'uplink_ipv4_pool' (10.0.2.0/24) - on this switch cannot be combined with 'downlink_pools' (10.0.2.0/24) on any uplink switch. + Unable to assign IPs for uplinks. 'uplink_ipv4_pool' (10.0.2.0/24) on this switch cannot be combined with + 'downlink_pools' (10.0.2.0/24) on any uplink switch for host 'downlink-pools-duplicate-l3leaf'. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_DOWNLINK_POOLS_MISSING.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_DOWNLINK_POOLS_MISSING.yml index c67553c527a..af8705d37c7 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_DOWNLINK_POOLS_MISSING.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_DOWNLINK_POOLS_MISSING.yml @@ -31,4 +31,4 @@ l3leaf: expected_error_message: >- Unable to assign IPs for uplinks. Either 'uplink_ipv4_pool' on this switch or 'downlink_pools' on - all the uplink switches is required but was not found for host 'downlink-pools-missing-l3leaf' + all the uplink switches must be set for host 'downlink-pools-missing-l3leaf'. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_1.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_1.yml index 0d1fa06132a..87a0856cbc0 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_1.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_1.yml @@ -19,9 +19,9 @@ l3leaf: - group: MLAG_PAIR bgp_as: 65123 nodes: - - name: invalid-uplink-port-channel-id-l3leaf-1 + - name: invalid-uplink-port-channel-id-1-l3leaf-1 id: 1 - - name: invalid-uplink-port-channel-id-l3leaf-2 + - name: invalid-uplink-port-channel-id-1-l3leaf-2 id: 2 l2leaf: @@ -29,18 +29,17 @@ l2leaf: mlag_peer_ipv4_pool: 10.0.2.0/24 mlag_interfaces: [Ethernet3-4] uplink_interfaces: [Ethernet1-2] - uplink_switches: [invalid-uplink-port-channel-id-l3leaf-1, invalid-uplink-port-channel-id-l3leaf-2] + uplink_switches: [invalid-uplink-port-channel-id-1-l3leaf-1, invalid-uplink-port-channel-id-1-l3leaf-2] uplink_port_channel_id: 2001 # <<<<< invalid ID node_groups: - group: MLAG_PAIR nodes: - - name: invalid-uplink-port-channel-id-l2leaf-1 + - name: invalid-uplink-port-channel-id-1-l2leaf-1 uplink_switch_interfaces: [Ethernet1, Ethernet1] id: 1 - - name: invalid-uplink-port-channel-id-l2leaf-2 + - name: invalid-uplink-port-channel-id-1-l2leaf-2 uplink_switch_interfaces: [Ethernet2, Ethernet2] id: 2 expected_error_message: >- - Unexpected failure during module execution: 'uplink_port_channel_id' must be between 1 and 2000 for MLAG switches. - Got '2001' on 'invalid-uplink-port-channel-id-l2leaf-1'. + 'uplink_port_channel_id' must be between 1 and 2000 for MLAG switches. Got '2001' for host 'invalid-uplink-port-channel-id-1-l2leaf-1'. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_2.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_2.yml index 2107a3291a5..728450f8d94 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_2.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_2.yml @@ -19,9 +19,9 @@ l3leaf: - group: MLAG_PAIR bgp_as: 65123 nodes: - - name: invalid-uplink-port-channel-id-l3leaf-1 + - name: invalid-uplink-port-channel-id-2-l3leaf-1 id: 1 - - name: invalid-uplink-port-channel-id-l3leaf-2 + - name: invalid-uplink-port-channel-id-2-l3leaf-2 id: 2 l2leaf: @@ -29,18 +29,17 @@ l2leaf: mlag_peer_ipv4_pool: 10.0.2.0/24 mlag_interfaces: [Ethernet3-4] uplink_interfaces: [Ethernet1-2] - uplink_switches: [invalid-uplink-port-channel-id-l3leaf-1, invalid-uplink-port-channel-id-l3leaf-2] + uplink_switches: [invalid-uplink-port-channel-id-2-l3leaf-1, invalid-uplink-port-channel-id-2-l3leaf-2] uplink_switch_port_channel_id: 2001 # <<<<< invalid ID node_groups: - group: MLAG_PAIR nodes: - - name: invalid-uplink-port-channel-id-l2leaf-1 + - name: invalid-uplink-port-channel-id-2-l2leaf-1 uplink_switch_interfaces: [Ethernet1, Ethernet1] id: 1 - - name: invalid-uplink-port-channel-id-l2leaf-2 + - name: invalid-uplink-port-channel-id-2-l2leaf-2 uplink_switch_interfaces: [Ethernet2, Ethernet2] id: 2 expected_error_message: >- - Unexpected failure during module execution: 'uplink_switch_port_channel_id' must be between 1 and 2000 for MLAG switches. - Got '2001' on 'invalid-uplink-port-channel-id-l2leaf-1'. + 'uplink_switch_port_channel_id' must be between 1 and 2000 for MLAG switches. Got '2001' for host 'invalid-uplink-port-channel-id-2-l2leaf-1'. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_3.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_3.yml index d6f0f5fa8a8..67f93baf421 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_3.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_3.yml @@ -19,9 +19,9 @@ l3leaf: - group: MLAG_PAIR bgp_as: 65123 nodes: - - name: invalid-uplink-port-channel-id-l3leaf-1 + - name: invalid-uplink-port-channel-id-3-l3leaf-1 id: 1 - - name: invalid-uplink-port-channel-id-l3leaf-2 + - name: invalid-uplink-port-channel-id-3-l3leaf-2 id: 2 l2leaf: @@ -29,19 +29,18 @@ l2leaf: mlag_peer_ipv4_pool: 10.0.2.0/24 mlag_interfaces: [Ethernet3-4] uplink_interfaces: [Ethernet1-2] - uplink_switches: [invalid-uplink-port-channel-id-l3leaf-1, invalid-uplink-port-channel-id-l3leaf-2] + uplink_switches: [invalid-uplink-port-channel-id-3-l3leaf-1, invalid-uplink-port-channel-id-3-l3leaf-2] node_groups: - group: MLAG_PAIR nodes: - - name: invalid-uplink-port-channel-id-l2leaf-1 + - name: invalid-uplink-port-channel-id-3-l2leaf-1 uplink_switch_interfaces: [Ethernet1, Ethernet1] id: 1 - - name: invalid-uplink-port-channel-id-l2leaf-2 + - name: invalid-uplink-port-channel-id-3-l2leaf-2 uplink_port_channel_id: 1234 # <<<<< valid ID but set to a different value than primary node uplink_switch_interfaces: [Ethernet2, Ethernet2] id: 2 expected_error_message: >- - Unexpected failure during module execution: 'uplink_port_channel_id' on 'invalid-uplink-port-channel-id-l2leaf-2' - is set to 1234 and is not matching 1 set on MLAG peer. - The 'uplink_port_channel_id' must be matching on MLAG peers. + 'uplink_port_channel_id' is set to 1234 and is not matching 1 set on MLAG peer. + The 'uplink_port_channel_id' must be matching on MLAG peers for host 'invalid-uplink-port-channel-id-3-l2leaf-2'. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_IP_ADDRESSING_MLAG_ODD_ID.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_IP_ADDRESSING_MLAG_ODD_ID.yml index 0eb4bdcce3d..d2a94073c74 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_IP_ADDRESSING_MLAG_ODD_ID.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_IP_ADDRESSING_MLAG_ODD_ID.yml @@ -25,4 +25,5 @@ l3leaf: id: 3 mgmt_ip: 192.168.201.117/24 -expected_error_message: "Unexpected failure during module execution: MLAG compact addressing mode requires all MLAG pairs to have a single odd and even ID" +expected_error_message: >- + MLAG compact addressing mode requires all MLAG pairs to have a single odd and even ID for host 'mlag_odd_id_oddodd1'. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_IP_ADDRESSING_MLAG_SAME_SUBNET.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_IP_ADDRESSING_MLAG_SAME_SUBNET.yml index 38e6e4e0a5a..a3828dda7f2 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_IP_ADDRESSING_MLAG_SAME_SUBNET.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/group_vars/FABRIC_IP_ADDRESSING_MLAG_SAME_SUBNET.yml @@ -25,4 +25,5 @@ l3leaf: id: 2 mgmt_ip: 192.168.201.117/24 -expected_error_message: "Unexpected failure during module execution: MLAG same_subnet addressing requires the pool to be a /31" +expected_error_message: >- + MLAG same_subnet addressing requires the pool to be a /31 for host 'mlag_same_subnet_1'. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/connected-endpoints-wrong-profile-lacp-fallback.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/connected-endpoints-wrong-profile-lacp-fallback.yml index 648903d9085..23f81150aad 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/connected-endpoints-wrong-profile-lacp-fallback.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/connected-endpoints-wrong-profile-lacp-fallback.yml @@ -28,4 +28,4 @@ port_profiles: native_vlan: 123 expected_error_message: >- - Profile 'INDIVIDUAL_TRUNK' applied under 'server[INDIVIDUAL_1].adapters[0].port_channel.lacp_fallback.individual' does not exist in `port_profiles`. + Profile 'INDIVIDUAL_TRUNK' applied under 'server[name=INDIVIDUAL_1].adapters[0].port_channel.lacp_fallback.individual' does not exist in `port_profiles`. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-adapter-ptp-profile-does-not-exist.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-adapter-ptp-profile-does-not-exist.yml index cc5df98a2b0..adc760f8185 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-adapter-ptp-profile-does-not-exist.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-adapter-ptp-profile-does-not-exist.yml @@ -29,5 +29,5 @@ servers: profile: THIS-PROFILE-DOES-NOT-EXIST expected_error_message: >- - PTP Profile 'THIS-PROFILE-DOES-NOT-EXIST' referenced under server[INDIVIDUAL_1].adapters[0] + PTP Profile 'THIS-PROFILE-DOES-NOT-EXIST' referenced under server[name=INDIVIDUAL_1].adapters[0] does not exist in `ptp_profiles`. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-connected-endpoint-port-profile-does-not-exist-in-facts.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-connected-endpoint-port-profile-does-not-exist-in-facts.yml new file mode 100644 index 00000000000..dd7701a4835 --- /dev/null +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-connected-endpoint-port-profile-does-not-exist-in-facts.yml @@ -0,0 +1,29 @@ +--- +type: l3leaf +fabric_name: FAILURE_CONNECTED_ENDPOINT_PORT_PROFILE_DOES_NOT_EXIST_IN_FACTS + +port_profiles: + - profile: PROFILE-1 + +l3leaf: + nodes: + - name: failure-connected-endpoint-port-profile-does-not-exist-in-facts + loopback_ipv4_pool: 192.168.42.0/24 + vtep_loopback_ipv4_pool: 192.168.255.0/24 + bgp_as: 65042 + id: 1 + filter: + only_vlans_in_use: True + +servers: + - name: TEST-ENDPOINT + adapters: + - switches: [failure-connected-endpoint-port-profile-does-not-exist-in-facts] + switch_ports: [Ethernet9] + profile: THIS-PROFILE-DOES-NOT-EXIST + # Setting VLANs to trigger merging of adapters in facts phase + vlans: :42,666" + +expected_error_message: >- + Profile 'THIS-PROFILE-DOES-NOT-EXIST' applied under 'servers[name=TEST-ENDPOINT].adapters[0]' + does not exist in `port_profiles` for host 'failure-connected-endpoint-port-profile-does-not-exist-in-facts'. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-connected-endpoint-port-profile-does-not-exist.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-connected-endpoint-port-profile-does-not-exist.yml index b0298e83227..7145c321901 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-connected-endpoint-port-profile-does-not-exist.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-connected-endpoint-port-profile-does-not-exist.yml @@ -20,5 +20,5 @@ servers: profile: THIS-PROFILE-DOES-NOT-EXIST expected_error_message: >- - Profile 'THIS-PROFILE-DOES-NOT-EXIST' applied under 'servers[TEST-ENDPOINT].adapters[0]' + Profile 'THIS-PROFILE-DOES-NOT-EXIST' applied under 'servers[name=TEST-ENDPOINT].adapters[0]' does not exist in `port_profiles`. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-svi-parent-profile-does-not-exist.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-svi-parent-profile-does-not-exist.yml index 914a605e682..e233419fcdb 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-svi-parent-profile-does-not-exist.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/failure-svi-parent-profile-does-not-exist.yml @@ -24,5 +24,4 @@ tenants: - node: failure-svi-parent-profile-does-not-exist expected_error_message: >- - Profile 'THIS-PROFILE-DOES-NOT-EXIST' applied under SVI 'TEST-SVI' does - not exist in `svi_profiles`. + Profile 'THIS-PROFILE-DOES-NOT-EXIST' applied under SVI 'TEST-SVI' does not exist in `svi_profiles`. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/invalid-uplink-type-p2p-vrfs-underlay-router-false.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/invalid-uplink-type-p2p-vrfs-underlay-router-false.yml index 2604806aded..3e5fde43c57 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/invalid-uplink-type-p2p-vrfs-underlay-router-false.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/invalid-uplink-type-p2p-vrfs-underlay-router-false.yml @@ -17,5 +17,5 @@ not_underlay_router: - name: invalid-uplink-type-p2p-vrfs-underlay-router-false expected_error_message: >- - Unexpected failure during module execution: 'underlay_router' and 'network_services.l3' - must be 'true' for the node_type_key when using 'p2p-vrfs' as 'uplink_type'. + 'underlay_router' and 'network_services.l3' must be 'true' for the node_type_key when using + 'p2p-vrfs' as 'uplink_type' for host 'invalid-uplink-type-p2p-vrfs-underlay-router-false'. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-avt-id-cv-pathfinder.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-avt-id-cv-pathfinder.yml index cf2a4a4902c..9191f7bf64c 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-avt-id-cv-pathfinder.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-avt-id-cv-pathfinder.yml @@ -95,4 +95,4 @@ application_classification: - name: TEST expected_error_message: >- - Missing mandatory `id` in `wan_virtual_topologies.policies[DEFAULT-POLICY].application_virtual_topologies[TEST]` when `wan_mode` is 'cv-pathfinder + Missing mandatory `id` in `wan_virtual_topologies.policies[DEFAULT-POLICY].application_virtual_topologies[TEST]` when `wan_mode` is 'cv-pathfinder. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-mlag-peer.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-mlag-peer.yml index d40d204ca60..22b44b329df 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-mlag-peer.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-mlag-peer.yml @@ -15,4 +15,4 @@ l2leaf: expected_error_message: >- Facts not found for node 'some-missing-device'. Something in the input vars is pointing to this node. Check that 'some-missing-device' is in the inventory and is part of the group set by 'fabric_name'. - Node is required but was not found for host 'missing-mlag-peer' + Node is required for host 'missing-mlag-peer'. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-prefix-list-definition-cv-pathfinder.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-prefix-list-definition-cv-pathfinder.yml index 3ced2688db9..4e605ec4d2a 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-prefix-list-definition-cv-pathfinder.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/host_vars/missing-prefix-list-definition-cv-pathfinder.yml @@ -85,4 +85,5 @@ wan_carriers: path_group: INET trusted: true -expected_error_message: "ipv4_prefix_list_catalog[name=PL1]" +expected_error_message: >- + 'ipv4_prefix_list_catalog[name=PL1]' is required but was not found. diff --git a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/hosts.yml b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/hosts.yml index 33ff7214929..cd164e47622 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/hosts.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_negative_unit_tests/inventory/hosts.yml @@ -24,24 +24,22 @@ all: invalid-schema-connected-endpoints: FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_1: hosts: - invalid-uplink-port-channel-id-l3leaf-1: - invalid-uplink-port-channel-id-l3leaf-2: - invalid-uplink-port-channel-id-l2leaf-1: - invalid-uplink-port-channel-id-l2leaf-2: + invalid-uplink-port-channel-id-1-l3leaf-1: + invalid-uplink-port-channel-id-1-l3leaf-2: + invalid-uplink-port-channel-id-1-l2leaf-1: + invalid-uplink-port-channel-id-1-l2leaf-2: FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_2: - # Notice the same hosts since all vars are given in group var hosts: - invalid-uplink-port-channel-id-l3leaf-1: - invalid-uplink-port-channel-id-l3leaf-2: - invalid-uplink-port-channel-id-l2leaf-1: - invalid-uplink-port-channel-id-l2leaf-2: + invalid-uplink-port-channel-id-2-l3leaf-1: + invalid-uplink-port-channel-id-2-l3leaf-2: + invalid-uplink-port-channel-id-2-l2leaf-1: + invalid-uplink-port-channel-id-2-l2leaf-2: FABRIC_INVALID_UPLINK_PORT_CHANNEL_ID_3: - # Notice the same hosts since all vars are given in group var hosts: - invalid-uplink-port-channel-id-l3leaf-1: - invalid-uplink-port-channel-id-l3leaf-2: - invalid-uplink-port-channel-id-l2leaf-1: - invalid-uplink-port-channel-id-l2leaf-2: + invalid-uplink-port-channel-id-3-l2leaf-1: + invalid-uplink-port-channel-id-3-l3leaf-1: + invalid-uplink-port-channel-id-3-l3leaf-2: + invalid-uplink-port-channel-id-3-l2leaf-2: FABRIC_WAN_ROLE_OVERLAY_ROUTING_PROTOCOL: hosts: invalid-wan-role-overlay-routing-protocol: @@ -56,6 +54,9 @@ all: hosts: downlink-pools-duplicate-spine: downlink-pools-duplicate-l3leaf: + FAILURE_CONNECTED_ENDPOINT_PORT_PROFILE_DOES_NOT_EXIST_IN_FACTS: + hosts: + failure-connected-endpoint-port-profile-does-not-exist-in-facts: EOS_DESIGNS_FAILURES: # Add cases that fail during 'eos_designs_structured_config' phase children: EOS_DESIGNS_FAILURES_INCLUDED: @@ -89,10 +90,10 @@ all: duplicate-interfaces-underlay: duplicate-ip-address-uplink-switch-router-bgp: duplicate-tunnel-interface-internet-exit: - failure-adapter-ptp-profile-does-not-exist: - failure-connected-endpoint-port-profile-does-not-exist: failure-connected-endpoint-parent-port-profile-does-not-exist: + failure-connected-endpoint-port-profile-does-not-exist: failure-duplicate-evpn-vlan-bundle-name: + failure-l3-interface-profile-does-not-exist: failure-missing-evpn-vlan-bundle: failure-missing-evpn-vlan-bundle_svi: failure-missing-evpn-multicast-l3-with-pim: @@ -100,9 +101,9 @@ all: failure-missing-evpn-multicast-with-pim: failure-network-port-ptp-profile-does-not-exist: failure-no-local-path-group-in-default-policy: - failure-ptp-profile-does-not-exist: - failure-svi-parent-profile-does-not-exist: + failure-adapter-ptp-profile-does-not-exist: failure-svi-grandparent-profile-does-not-exist: + failure-svi-parent-profile-does-not-exist: ipv4-acl-in-missing-on-wan-interface: ipv4-acls: isis-system-id-format-missing-node-id: 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 f11bfc2f9e4..5bc23ce559f 100644 --- a/ansible_collections/arista/avd/plugins/action/eos_designs_facts.py +++ b/ansible_collections/arista/avd/plugins/action/eos_designs_facts.py @@ -19,7 +19,7 @@ try: from pyavd._eos_designs.eos_designs_facts import EosDesignsFacts from pyavd._eos_designs.shared_utils import SharedUtils - from pyavd._errors import AristaAvdMissingVariableError + from pyavd._errors import AristaAvdError except ImportError as e: EosDesignsFacts = SharedUtils = RaiseOnUse( AnsibleActionFail( @@ -194,9 +194,9 @@ def render_avd_switch_facts(self, avd_switch_facts_instances: dict) -> dict: for host in avd_switch_facts_instances: try: rendered_facts[host] = {"switch": avd_switch_facts_instances[host]["switch"].render()} - except AristaAvdMissingVariableError as e: - msg = f"{e} is required but was not found for host '{host}'" - raise AnsibleActionFail(msg) from e + except AristaAvdError as e: + message = f"{str(e).removesuffix('.')} for host '{host}'." + raise AnsibleActionFail(message=message) from e # If the argument 'template_output' is set, run the output data through jinja2 rendering. # This is to resolve any input values with inline jinja using variables/facts set by eos_designs_facts. diff --git a/python-avd/pyavd/_eos_designs/eos_designs_facts/uplinks.py b/python-avd/pyavd/_eos_designs/eos_designs_facts/uplinks.py index eac4766e8f2..410f0f5fe63 100644 --- a/python-avd/pyavd/_eos_designs/eos_designs_facts/uplinks.py +++ b/python-avd/pyavd/_eos_designs/eos_designs_facts/uplinks.py @@ -53,8 +53,7 @@ def _uplink_port_channel_id(self: EosDesignsFacts) -> int: # check that port-channel IDs are the same as on primary if uplink_port_channel_id is not None and uplink_port_channel_id != peer_uplink_port_channel_id: msg = ( - f"'uplink_port_channel_id' on '{self.shared_utils.hostname}' is set to {uplink_port_channel_id} and is not matching" - f" {peer_uplink_port_channel_id} set on MLAG peer." + f"'uplink_port_channel_id' is set to {uplink_port_channel_id} and is not matching {peer_uplink_port_channel_id} set on MLAG peer." " The 'uplink_port_channel_id' must be matching on MLAG peers." ) raise AristaAvdError(msg) @@ -67,7 +66,7 @@ def _uplink_port_channel_id(self: EosDesignsFacts) -> int: # produce an error if the switch is MLAG and port-channel ID is above 2000 if self.shared_utils.mlag and not 1 <= uplink_port_channel_id <= 2000: - msg = f"'uplink_port_channel_id' must be between 1 and 2000 for MLAG switches. Got '{uplink_port_channel_id}' on '{self.shared_utils.hostname}'." + msg = f"'uplink_port_channel_id' must be between 1 and 2000 for MLAG switches. Got '{uplink_port_channel_id}'." raise AristaAvdError(msg) return uplink_port_channel_id @@ -93,9 +92,8 @@ def _uplink_switch_port_channel_id(self: EosDesignsFacts) -> int: # check that port-channel IDs are the same as on primary if uplink_switch_port_channel_id is not None and uplink_switch_port_channel_id != peer_uplink_switch_port_channel_id: msg = ( - f"'uplink_switch_port_channel_id'expected_error_message on '{self.shared_utils.hostname}' is set to {uplink_switch_port_channel_id} and" - f" is not matching {peer_uplink_switch_port_channel_id} set on MLAG peer. The 'uplink_switch_port_channel_id' must be matching on MLAG" - " peers." + f"'uplink_switch_port_channel_id' is set to {uplink_switch_port_channel_id} and is not matching {peer_uplink_switch_port_channel_id} " + "set on MLAG peer. The 'uplink_switch_port_channel_id' must be matching on MLAG peers." ) raise AristaAvdError(msg) return peer_uplink_switch_port_channel_id @@ -109,10 +107,7 @@ def _uplink_switch_port_channel_id(self: EosDesignsFacts) -> int: uplink_switch_facts: EosDesignsFacts = self.shared_utils.get_peer_facts(self.shared_utils.uplink_switches[0], required=True) if uplink_switch_facts.shared_utils.mlag and not 1 <= uplink_switch_port_channel_id <= 2000: - msg = ( - f"'uplink_switch_port_channel_id' must be between 1 and 2000 for MLAG switches. Got '{uplink_switch_port_channel_id}' on" - f" '{self.shared_utils.hostname}'." - ) + msg = f"'uplink_switch_port_channel_id' must be between 1 and 2000 for MLAG switches. Got '{uplink_switch_port_channel_id}'." raise AristaAvdError(msg) return uplink_switch_port_channel_id diff --git a/python-avd/pyavd/_eos_designs/eos_designs_facts/vlans.py b/python-avd/pyavd/_eos_designs/eos_designs_facts/vlans.py index 8c4222a2577..5969978029c 100644 --- a/python-avd/pyavd/_eos_designs/eos_designs_facts/vlans.py +++ b/python-avd/pyavd/_eos_designs/eos_designs_facts/vlans.py @@ -86,9 +86,9 @@ def _local_endpoint_vlans_and_trunk_groups(self: EosDesignsFacts) -> tuple[set, for connected_endpoints_key in self.shared_utils.connected_endpoints_keys: connected_endpoints = get(self._hostvars, connected_endpoints_key["key"], default=[]) for connected_endpoint in connected_endpoints: - for adapter in connected_endpoint.get("adapters", []): + for index, adapter in enumerate(connected_endpoint.get("adapters", [])): adapter_settings = self.shared_utils.get_merged_adapter_settings( - adapter, context=f"{connected_endpoints_key['key']}[{connected_endpoint['name']}].adapters" + adapter, context=f"{connected_endpoints_key['key']}[name={connected_endpoint['name']}].adapters[{index}]" ) if self.shared_utils.hostname not in adapter_settings.get("switches", []): # This switch is not connected to this endpoint. Skipping. @@ -104,7 +104,7 @@ def _local_endpoint_vlans_and_trunk_groups(self: EosDesignsFacts) -> tuple[set, return vlans, trunk_groups network_ports = get(self._hostvars, "network_ports", default=[]) - for network_port_item in network_ports: + for index, network_port_item in enumerate(network_ports): for switch_regex in network_port_item.get("switches", []): # The match test is built on Python re.match which tests from the beginning of the string #} # Since the user would not expect "DC1-LEAF1" to also match "DC-LEAF11" we will force ^ and $ around the regex @@ -113,7 +113,7 @@ def _local_endpoint_vlans_and_trunk_groups(self: EosDesignsFacts) -> tuple[set, # Skip entry if no match continue - adapter_settings = self.shared_utils.get_merged_adapter_settings(network_port_item, context="network_ports") + adapter_settings = self.shared_utils.get_merged_adapter_settings(network_port_item, context=f"network_ports[{index}]") adapter_vlans, adapter_trunk_groups = self._parse_adapter_settings(adapter_settings) vlans.update(adapter_vlans) trunk_groups.update(adapter_trunk_groups) diff --git a/python-avd/pyavd/_eos_designs/shared_utils/cv_topology.py b/python-avd/pyavd/_eos_designs/shared_utils/cv_topology.py index 59c517d8140..9d89ae80744 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/cv_topology.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/cv_topology.py @@ -45,7 +45,7 @@ def cv_topology(self: SharedUtils) -> dict | None: self.hostvars, "cv_topology", required=True, - org_key="Found 'use_cv_topology:true' so 'cv_topology'", + custom_error_msg="Found 'use_cv_topology:true' so 'cv_topology' is required.", ) return get_item(cv_topology, "hostname", self.hostname) @@ -79,7 +79,7 @@ def cv_topology_config(self: SharedUtils) -> dict: self.default_interfaces, "uplink_interfaces", required=True, - org_key="Found 'use_cv_topology:true' so 'default_interfaces.[].uplink_interfaces'", + custom_error_msg="Found 'use_cv_topology:true' so 'default_interfaces.[].uplink_interfaces' is required.", ), ) config = {} @@ -97,7 +97,7 @@ def cv_topology_config(self: SharedUtils) -> dict: self.default_interfaces, "mlag_interfaces", required=True, - org_key="Found 'use_cv_topology:true' so 'default_interfaces.[].mlag_interfaces'", + custom_error_msg="Found 'use_cv_topology:true' so 'default_interfaces.[].mlag_interfaces' is required.", ), ) for mlag_interface in default_mlag_interfaces: 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 d79f6d3eaca..d81c04eb870 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/filtered_tenants.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/filtered_tenants.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import default, get, get_item, merge, unique from pyavd.j2filters import natural_sort, range_expand @@ -230,7 +230,7 @@ def filtered_vrfs(self: SharedUtils, tenant: dict) -> list[dict]: rp_entry, "rps", required=True, - org_key=f"pim_rp_addresses.rps under VRF '{vrf['name']}' in Tenant '{tenant['name']}'", + custom_error_msg=f"'pim_rp_addresses.rps' under VRF '{vrf['name']}' in Tenant '{tenant['name']}' is required.", ): rp_address = {"address": rp_ip} if (rp_groups := get(rp_entry, "groups")) is not None: @@ -370,7 +370,7 @@ def get_vrf_id(vrf: dict, required: bool = True) -> int | None: if vrf_id is None: if required: msg = f"'vrf_id' or 'vrf_vni' for VRF '{vrf['name']} must be set." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) return None return int(vrf_id) @@ -379,7 +379,7 @@ def get_vrf_vni(vrf: dict) -> int: vrf_vni = default(vrf.get("vrf_vni"), vrf.get("vrf_id")) if vrf_vni is None: msg = f"'vrf_vni' or 'vrf_id' for VRF '{vrf['name']} must be set." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) return int(vrf_vni) @cached_property diff --git a/python-avd/pyavd/_eos_designs/shared_utils/inband_management.py b/python-avd/pyavd/_eos_designs/shared_utils/inband_management.py index 045ee0bf49f..e194a4990d0 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/inband_management.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/inband_management.py @@ -7,7 +7,7 @@ from ipaddress import ip_network from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import default, get if TYPE_CHECKING: @@ -130,7 +130,7 @@ def inband_mgmt_ip(self: SharedUtils) -> str | None: if self.id is None: msg = f"'id' is not set on '{self.hostname}' and is required to set inband_mgmt_ip from inband_mgmt_subnet" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) subnet = ip_network(self.inband_mgmt_subnet, strict=False) inband_mgmt_ip = str(subnet[3 + self.id]) @@ -154,7 +154,7 @@ def inband_mgmt_ipv6_address(self: SharedUtils) -> str | None: if self.id is None: msg = f"'id' is not set on '{self.hostname}' and is required to set inband_mgmt_ipv6_address from inband_mgmt_ipv6_subnet" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) subnet = ip_network(self.inband_mgmt_ipv6_subnet, strict=False) inband_mgmt_ipv6_address = str(subnet[3 + self.id]) diff --git a/python-avd/pyavd/_eos_designs/shared_utils/l3_interfaces.py b/python-avd/pyavd/_eos_designs/shared_utils/l3_interfaces.py index 298db2b4c87..1d4a8f415d9 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/l3_interfaces.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/l3_interfaces.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import get, get_item, merge from pyavd.api.interface_descriptions import InterfaceDescriptionData @@ -71,14 +71,14 @@ def l3_interfaces_bgp_neighbors(self: SharedUtils) -> list: peer_as = get(bgp, "peer_as") if peer_as is None: msg = f"'l3_interfaces[{interface['name']}].bgp.peer_as' needs to be set to enable BGP." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) is_intf_wan = get(interface, "wan_carrier") is not None prefix_list_in = get(bgp, "ipv4_prefix_list_in") if prefix_list_in is None and is_intf_wan: msg = f"BGP is enabled but 'bgp.ipv4_prefix_list_in' is not configured for l3_interfaces[{interface['name']}]" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) description = interface.get("description") if not description: diff --git a/python-avd/pyavd/_eos_designs/shared_utils/mgmt.py b/python-avd/pyavd/_eos_designs/shared_utils/mgmt.py index 4248f183431..5ef29601f15 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/mgmt.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/mgmt.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import default, get if TYPE_CHECKING: @@ -70,7 +70,7 @@ def default_mgmt_method(self: SharedUtils) -> str | None: if default_mgmt_method == "oob": if (self.mgmt_ip is None) and (self.ipv6_mgmt_ip is None): msg = "'default_mgmt_method: oob' requires either 'mgmt_ip' or 'ipv6_mgmt_ip' to bet set." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) return default_mgmt_method @@ -78,7 +78,7 @@ def default_mgmt_method(self: SharedUtils) -> str | None: # Check for missing interface if self.inband_mgmt_interface is None: msg = "'default_mgmt_method: inband' requires 'inband_mgmt_interface' to be set." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) return default_mgmt_method diff --git a/python-avd/pyavd/_eos_designs/shared_utils/misc.py b/python-avd/pyavd/_eos_designs/shared_utils/misc.py index 74c80997699..981d11abfb3 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/misc.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/misc.py @@ -7,7 +7,7 @@ from functools import cached_property from typing import TYPE_CHECKING, Any -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import default, get from pyavd.j2filters import natural_sort, range_expand @@ -136,7 +136,7 @@ def uplink_switch_interfaces(self: SharedUtils) -> list: if self.id is None: msg = f"'id' is not set on '{self.hostname}'" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) uplink_switch_interfaces = [] uplink_switch_counter = {} diff --git a/python-avd/pyavd/_eos_designs/shared_utils/mlag.py b/python-avd/pyavd/_eos_designs/shared_utils/mlag.py index 119851af576..789db8f19fe 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/mlag.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/mlag.py @@ -7,7 +7,7 @@ from re import findall from typing import TYPE_CHECKING, Any -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import default, get, get_ip_from_ip_prefix from pyavd.j2filters import range_expand @@ -168,12 +168,12 @@ def mlag_switch_ids(self: SharedUtils) -> dict | None: if self.mlag_role == "primary": if self.id is None: msg = f"'id' is not set on '{self.hostname}' and is required to compute MLAG ids" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) return {"primary": self.id, "secondary": self.mlag_peer_id} if self.mlag_role == "secondary": if self.id is None: msg = f"'id' is not set on '{self.hostname}' and is required to compute MLAG ids" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) return {"primary": self.mlag_peer_id, "secondary": self.id} return None @@ -181,7 +181,7 @@ def mlag_switch_ids(self: SharedUtils) -> dict | None: def mlag_port_channel_id(self: SharedUtils) -> int: if not self.mlag_interfaces: msg = f"'mlag_interfaces' not set on '{self.hostname}." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) default_mlag_port_channel_id = int("".join(findall(r"\d", self.mlag_interfaces[0]))) return get(self.switch_data_combined, "mlag_port_channel_id", default_mlag_port_channel_id) diff --git a/python-avd/pyavd/_eos_designs/shared_utils/node_type.py b/python-avd/pyavd/_eos_designs/shared_utils/node_type.py index 28bb143f060..382774f821e 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/node_type.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/node_type.py @@ -7,7 +7,7 @@ from re import search from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import get if TYPE_CHECKING: @@ -31,7 +31,7 @@ def type(self: SharedUtils) -> str: return self.default_node_type msg = f"'type' for host {self.hostname}" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) @cached_property def default_node_type(self: SharedUtils) -> str | None: diff --git a/python-avd/pyavd/_eos_designs/shared_utils/node_type_keys.py b/python-avd/pyavd/_eos_designs/shared_utils/node_type_keys.py index f56708aa8f9..29dda7276e6 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/node_type_keys.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/node_type_keys.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import get, get_item, replace_or_append_item if TYPE_CHECKING: @@ -214,4 +214,4 @@ def node_type_key_data(self: SharedUtils) -> dict: # Not found msg = f"node_type_keys.[type=={self.type}]" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) diff --git a/python-avd/pyavd/_eos_designs/shared_utils/overlay.py b/python-avd/pyavd/_eos_designs/shared_utils/overlay.py index 92ec06a22d2..2f860e33503 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/overlay.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/overlay.py @@ -8,7 +8,7 @@ from re import fullmatch from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import get if TYPE_CHECKING: @@ -91,7 +91,7 @@ def get_rd_admin_subfield_value(self: SharedUtils, admin_subfield: str, admin_su if admin_subfield == "switch_id": if self.id is None: msg = f"'id' is not set on '{self.hostname}' and 'overlay_rd_type_admin_subfield' is set to 'switch_id'" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) return self.id + admin_subfield_offset if fullmatch(r"\d+", str(admin_subfield)): diff --git a/python-avd/pyavd/_eos_designs/shared_utils/routing.py b/python-avd/pyavd/_eos_designs/shared_utils/routing.py index 096157299b0..b52c9888846 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/routing.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/routing.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import get from pyavd.j2filters import range_expand @@ -103,7 +103,7 @@ def bgp_as(self: SharedUtils) -> str | None: if self.id is None: msg = f"'id' is not set on '{self.hostname}' and is required when expanding 'bgp_as'" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) return bgp_as_range_expanded[self.id - 1] except IndexError as exc: msg = f"Unable to allocate BGP AS: bgp_as range is too small ({len(bgp_as_range_expanded)}) for the id of the device" diff --git a/python-avd/pyavd/_eos_designs/shared_utils/utils.py b/python-avd/pyavd/_eos_designs/shared_utils/utils.py index b611d54dc2a..8d1b71e7bdc 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/utils.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/utils.py @@ -37,9 +37,9 @@ def get_peer_facts(self: SharedUtils, peer_name: str, required: bool = True) -> f"avd_switch_facts..{peer_name}..switch", separator="..", required=required, - org_key=( + custom_error_msg=( f"Facts not found for node '{peer_name}'. Something in the input vars is pointing to this node. " - f"Check that '{peer_name}' is in the inventory and is part of the group set by 'fabric_name'. Node" + f"Check that '{peer_name}' is in the inventory and is part of the group set by 'fabric_name'. Node is required." ), ) diff --git a/python-avd/pyavd/_eos_designs/shared_utils/wan.py b/python-avd/pyavd/_eos_designs/shared_utils/wan.py index 6876a9d1a1b..2d4d07d6ad8 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/wan.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/wan.py @@ -7,7 +7,7 @@ from re import findall from typing import TYPE_CHECKING, Literal -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import default, get, get_ip_from_ip_prefix, get_item, strip_empties_from_dict from pyavd.j2filters import natural_sort @@ -253,7 +253,7 @@ def wan_site(self: SharedUtils) -> dict | None: self.switch_data_combined, "cv_pathfinder_site", required=self.is_cv_pathfinder_client, - org_key="A node variable 'cv_pathfinder_site' must be defined when 'wan_role' is 'client' and 'wan_mode' is 'cv-pathfinder'", + custom_error_msg="A node variable 'cv_pathfinder_site' must be defined when 'wan_role' is 'client' and 'wan_mode' is 'cv-pathfinder'.", ) if node_defined_site is None: return None @@ -265,7 +265,9 @@ def wan_site(self: SharedUtils) -> dict | None: f"The 'cv_pathfinder_site '{node_defined_site}' defined at the node level could not be found under the 'cv_pathfinder_global_sites' list" ) else: - sites = get(self.wan_region, "sites", required=True, org_key=f"The CV Pathfinder region '{self.wan_region['name']}' is missing a list of sites") + sites = get( + self.wan_region, "sites", required=True, custom_error_msg=f"The CV Pathfinder region '{self.wan_region['name']}' is missing a list of sites." + ) site_not_found_error_msg = ( ( f"The 'cv_pathfinder_site '{node_defined_site}' defined at the node level could not be found under the 'sites' list for the region" @@ -292,7 +294,7 @@ def wan_region(self: SharedUtils) -> dict | None: self.switch_data_combined, "cv_pathfinder_region", required=self.is_cv_pathfinder_client, - org_key="A node variable 'cv_pathfinder_region' must be defined when 'wan_role' is 'client' and 'wan_mode' is 'cv-pathfinder'", + custom_error_msg="A node variable 'cv_pathfinder_region' must be defined when 'wan_role' is 'client' and 'wan_mode' is 'cv-pathfinder'.", ) if node_defined_region is None: return None @@ -301,7 +303,7 @@ def wan_region(self: SharedUtils) -> dict | None: self.hostvars, "cv_pathfinder_regions", required=True, - org_key="'cv_pathfinder_regions' key must be set when 'wan_mode' is 'cv-pathfinder'.", + custom_error_msg="'cv_pathfinder_regions' key must be set when 'wan_mode' is 'cv-pathfinder'.", ) return get_item( @@ -361,13 +363,13 @@ def filtered_wan_route_servers(self: SharedUtils) -> dict: f"'vtep_ip' is missing for peering with {wan_rs}, either set it in under 'wan_route_servers' or something is wrong with the peer" " facts." ) - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) if wan_path_groups is None: msg = ( f"'wan_path_groups' is missing for peering with {wan_rs}, either set it in under 'wan_route_servers'" " or something is wrong with the peer facts." ) - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) else: # Retrieve the values from the dictionary, making them required if the peer_facts were not found vtep_ip = get(wan_rs_dict, "vtep_ip", required=True) @@ -375,7 +377,7 @@ def filtered_wan_route_servers(self: SharedUtils) -> dict: wan_rs_dict, "path_groups", required=True, - org_key=( + custom_error_msg=( f"'path_groups' is missing for peering with {wan_rs} which was not found in the inventory, either set it in under 'wan_route_servers'" " or check your inventory." ), @@ -587,8 +589,8 @@ def wan_ha_peer_ip_addresses(self: SharedUtils) -> list: uplink, "ip_address", required=True, - org_key=( - f"The uplink interface {uplink['interface']} used as WAN LAN HA on the remote peer {self.wan_ha_peer} does not have an IP address", + custom_error_msg=( + f"The uplink interface {uplink['interface']} used as WAN LAN HA on the remote peer {self.wan_ha_peer} does not have an IP address.", ), ) prefix_length = uplink["prefix_length"] @@ -615,7 +617,7 @@ def wan_ha_ip_addresses(self: SharedUtils) -> list: uplink, "ip_address", required=True, - org_key=f"The uplink interface {uplink['interface']} used as WAN LAN HA does not have an IP address", + custom_error_msg=f"The uplink interface {uplink['interface']} used as WAN LAN HA does not have an IP address.", ) # We can use [] notation here because if there is an ip_address, there should be a prefix_length prefix_length = uplink["prefix_length"] @@ -632,7 +634,7 @@ def wan_ha_ipv4_pool(self: SharedUtils) -> str: self.switch_data_combined, "wan_ha.ha_ipv4_pool", required=True, - org_key="Missing `wan_ha.ha_ipv4_pool` node settings to allocate an IP address to defined HA interface", + custom_error_msg="Missing `wan_ha.ha_ipv4_pool` node settings to allocate an IP address to defined HA interface.", ) def generate_lb_policy_name(self: SharedUtils, name: str) -> str: diff --git a/python-avd/pyavd/_eos_designs/structured_config/base/__init__.py b/python-avd/pyavd/_eos_designs/structured_config/base/__init__.py index 62554e10f06..326dff8d29b 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/base/__init__.py +++ b/python-avd/pyavd/_eos_designs/structured_config/base/__init__.py @@ -6,7 +6,7 @@ from functools import cached_property from pyavd._eos_designs.avdfacts import AvdFacts -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import get, strip_null_from_data from pyavd.j2filters import natural_sort @@ -511,7 +511,7 @@ def platform(self) -> dict | None: # For AutoVPN Route Reflectors and Pathfinders, running on CloudEOS, setting # this value is required for the solution to work. msg = "For AutoVPN RRs and Pathfinders, 'data_plane_cpu_allocation_max' must be set" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) if platform: return platform @@ -573,7 +573,7 @@ def lacp(self) -> dict | None: if (switch_id := self.shared_utils.id) is None: msg = f"'id' is not set on '{self.shared_utils.hostname}' to set LACP port ID ranges" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) node_group_length = max(len(self.shared_utils.switch_data_node_group_nodes), 1) port_range = int(get(lacp_port_id_range, "size", default=128)) @@ -615,7 +615,7 @@ def ptp(self) -> dict | None: if priority2 is None: if self.shared_utils.id is None: msg = f"'id' must be set on '{self.shared_utils.hostname}' to set ptp priority2" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) priority2 = self.shared_utils.id % 256 default_auto_clock_identity = get(self._hostvars, "ptp_settings.auto_clock_identity", default=True) diff --git a/python-avd/pyavd/_eos_designs/structured_config/base/snmp_server.py b/python-avd/pyavd/_eos_designs/structured_config/base/snmp_server.py index d6c4e6a2ed4..4ec9841975e 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/base/snmp_server.py +++ b/python-avd/pyavd/_eos_designs/structured_config/base/snmp_server.py @@ -7,7 +7,7 @@ from hashlib import sha1 from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import get, replace_or_append_item, strip_null_from_data from pyavd.j2filters import natural_sort, snmp_hash @@ -80,8 +80,8 @@ def _snmp_engine_ids(self: AvdStructuredConfigBase, snmp_settings: dict) -> dict local_engine_id = sha1(f"{self.shared_utils.hostname}{self.shared_utils.mgmt_ip}".encode()).hexdigest() # NOSONAR # noqa: S324 elif compute_source == "system_mac": if self.shared_utils.system_mac_address is None: - msg = "default_engine_id_from_system_mac: true requires system_mac_address to be set!" - raise AristaAvdMissingVariableError(msg) + msg = "default_engine_id_from_system_mac: true requires system_mac_address to be set." + raise AristaAvdInvalidInputsError(msg) # the default engine id on switches is derived as per the following formula local_engine_id = f"f5717f{str(self.shared_utils.system_mac_address).replace(':', '').lower()}00" else: diff --git a/python-avd/pyavd/_eos_designs/structured_config/base/utils.py b/python-avd/pyavd/_eos_designs/structured_config/base/utils.py index 41a625cc860..b8f9d28aaf1 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/base/utils.py +++ b/python-avd/pyavd/_eos_designs/structured_config/base/utils.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import get if TYPE_CHECKING: @@ -37,7 +37,7 @@ def _build_source_interfaces(self: AvdStructuredConfigBase, include_mgmt_interfa if include_mgmt_interface: if (self.shared_utils.mgmt_ip is None) and (self.shared_utils.ipv6_mgmt_ip is None): msg = f"Unable to configure {error_context} source-interface since 'mgmt_ip' or 'ipv6_mgmt_ip' are not set." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) # mgmt_interface is always set (defaults to "Management1") so no need for error handling missing interface. source_interface = {"name": self.shared_utils.mgmt_interface} @@ -49,7 +49,7 @@ def _build_source_interfaces(self: AvdStructuredConfigBase, include_mgmt_interfa # Check for missing interface if self.shared_utils.inband_mgmt_interface is None: msg = f"Unable to configure {error_context} source-interface since 'inband_mgmt_interface' is not set." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) # Check for duplicate VRF # inband_mgmt_vrf returns None in case of VRF "default", but here we want the "default" VRF name to have proper duplicate detection. diff --git a/python-avd/pyavd/_eos_designs/structured_config/connected_endpoints/ethernet_interfaces.py b/python-avd/pyavd/_eos_designs/structured_config/connected_endpoints/ethernet_interfaces.py index 12ee6b9bc66..8cd0e6e2c67 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/connected_endpoints/ethernet_interfaces.py +++ b/python-avd/pyavd/_eos_designs/structured_config/connected_endpoints/ethernet_interfaces.py @@ -8,7 +8,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import append_if_not_duplicate, default, get, replace_or_append_item, strip_null_from_data from pyavd.api.interface_descriptions import InterfaceDescriptionData from pyavd.j2filters import range_expand @@ -66,7 +66,7 @@ def ethernet_interfaces(self: AvdStructuredConfigConnectedEndpoints) -> list | N if node_name != self.shared_utils.hostname: continue - context = f"{connected_endpoint['type']}[{connected_endpoint['name']}].adapters[{index}]" + context = f"{connected_endpoint['type']}[name={connected_endpoint['name']}].adapters[{index}]" ethernet_interface = self._get_ethernet_interface_cfg(adapter, node_index, connected_endpoint, context) append_if_not_duplicate( list_of_dicts=non_overwritable_ethernet_interfaces, @@ -194,9 +194,7 @@ def _get_ethernet_interface_cfg( "A Port-channel which is set to lacp fallback mode 'individual' must have a 'profile' defined. Profile definition is missing for" f" the connected endpoint with the name '{connected_endpoint['name']}'." ) - raise AristaAvdMissingVariableError( - msg, - ) + raise AristaAvdInvalidInputsError(msg) profile = self.shared_utils.get_merged_port_profile(profile_name, context=f"{context}.port_channel.lacp_fallback.individual") diff --git a/python-avd/pyavd/_eos_designs/structured_config/connected_endpoints/utils.py b/python-avd/pyavd/_eos_designs/structured_config/connected_endpoints/utils.py index 994caa94af7..404d763c0c7 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/connected_endpoints/utils.py +++ b/python-avd/pyavd/_eos_designs/structured_config/connected_endpoints/utils.py @@ -39,7 +39,7 @@ def _filtered_connected_endpoints(self: AvdStructuredConfigConnectedEndpoints) - filtered_adapters = [] for adapter_index, adapter in enumerate(connected_endpoint["adapters"]): adapter_settings = self.shared_utils.get_merged_adapter_settings( - adapter, context=f"{connected_endpoints_key['key']}[{connected_endpoint['name']}].adapters[{adapter_index}]" + adapter, context=f"{connected_endpoints_key['key']}[name={connected_endpoint['name']}].adapters[{adapter_index}]" ) if self.shared_utils.hostname not in adapter_settings.get("switches", []): @@ -125,7 +125,9 @@ def _get_short_esi( def _get_adapter_trunk_groups(self: AvdStructuredConfigConnectedEndpoints, adapter: dict, connected_endpoint: dict) -> dict | None: """Return trunk_groups for one adapter.""" if self.shared_utils.enable_trunk_groups and "trunk" in adapter.get("mode", ""): - return get(adapter, "trunk_groups", required=True, org_key=f"'trunk_groups' for the connected_endpoint {connected_endpoint['name']}") + return get( + adapter, "trunk_groups", required=True, custom_error_msg=f"'trunk_groups' for the connected_endpoint {connected_endpoint['name']} is required." + ) return None @@ -175,7 +177,7 @@ def _get_adapter_evpn_ethernet_segment_cfg( adapter_ethernet_segment, "designated_forwarder_preferences", required=True, - org_key=f"ethernet_segment.designated_forwarder_preferences for the connected_endpoint {connected_endpoint['name']}", + custom_error_msg=f"ethernet_segment.designated_forwarder_preferences for the connected_endpoint {connected_endpoint['name']}.", ) evpn_ethernet_segment["designated_forwarder_election"] = { "algorithm": "preference", diff --git a/python-avd/pyavd/_eos_designs/structured_config/core_interfaces_and_l3_edge/router_bgp.py b/python-avd/pyavd/_eos_designs/structured_config/core_interfaces_and_l3_edge/router_bgp.py index 65fd87cf656..dfdd7b2b550 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/core_interfaces_and_l3_edge/router_bgp.py +++ b/python-avd/pyavd/_eos_designs/structured_config/core_interfaces_and_l3_edge/router_bgp.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import get_ip_from_ip_prefix from .utils import UtilsMixin @@ -36,7 +36,7 @@ def router_bgp(self: AvdStructuredConfigCoreInterfacesAndL3Edge) -> dict | None: if p2p_link["data"]["bgp_as"] is None or p2p_link["data"]["peer_bgp_as"] is None: msg = f"{self.data_model}.p2p_links.[].as or {self.data_model}.p2p_links_profiles.[].as" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) neighbor = { "remote_as": p2p_link["data"]["peer_bgp_as"], @@ -53,7 +53,7 @@ def router_bgp(self: AvdStructuredConfigCoreInterfacesAndL3Edge) -> dict | None: # Regular BGP Neighbors if p2p_link["data"]["ip"] is None or p2p_link["data"]["peer_ip"] is None: msg = f"{self.data_model}.p2p_links.[].ip, .subnet or .ip_pool" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) neighbor["bfd"] = p2p_link.get("bfd") if p2p_link["data"]["bgp_as"] != self.shared_utils.bgp_as: diff --git a/python-avd/pyavd/_eos_designs/structured_config/core_interfaces_and_l3_edge/utils.py b/python-avd/pyavd/_eos_designs/structured_config/core_interfaces_and_l3_edge/utils.py index e883d0f8cd1..3a4c86ed05b 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/core_interfaces_and_l3_edge/utils.py +++ b/python-avd/pyavd/_eos_designs/structured_config/core_interfaces_and_l3_edge/utils.py @@ -10,7 +10,7 @@ from itertools import islice from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import default, get, get_item, merge if TYPE_CHECKING: @@ -201,7 +201,7 @@ def _get_p2p_data(self: AvdStructuredConfigCoreInterfacesAndL3Edge, p2p_link: di return data msg = f"{self.data_model}.p2p_links must have either 'interfaces' or 'port_channel' with correct members set." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) def _get_common_interface_cfg(self: AvdStructuredConfigCoreInterfacesAndL3Edge, p2p_link: dict) -> dict: """ diff --git a/python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py b/python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py index 0877130df95..973fb3f03ac 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py +++ b/python-avd/pyavd/_eos_designs/structured_config/flows/__init__.py @@ -6,7 +6,7 @@ from functools import cached_property from pyavd._eos_designs.avdfacts import AvdFacts -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import get, get_item, strip_null_from_data from pyavd.j2filters import natural_sort @@ -38,7 +38,7 @@ def sflow(self) -> dict | None: destinations = get(self._hostvars, "sflow_settings.destinations") if destinations is None: msg = "`sflow_settings.destinations` is required to configure `sflow`." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) sflow_settings_vrfs = get(self._hostvars, "sflow_settings.vrfs", default=[]) @@ -61,9 +61,7 @@ def sflow(self) -> dict | None: elif vrf == "use_mgmt_interface_vrf": if (self.shared_utils.mgmt_ip is None) and (self.shared_utils.ipv6_mgmt_ip is None): msg = "Unable to configure sFlow source-interface with 'use_mgmt_interface_vrf' since 'mgmt_ip' or 'ipv6_mgmt_ip' are not set." - raise AristaAvdMissingVariableError( - msg, - ) + raise AristaAvdInvalidInputsError(msg) vrf = self.shared_utils.mgmt_interface_vrf source_interface = get(get_item(sflow_settings_vrfs, "name", vrf, default={}), "source_interface", default=self.shared_utils.mgmt_interface) @@ -72,9 +70,7 @@ def sflow(self) -> dict | None: # Check for missing interface if self.shared_utils.inband_mgmt_interface is None: msg = "Unable to configure sFlow source-interface with 'use_inband_mgmt_vrf' since 'inband_mgmt_interface' is not set." - raise AristaAvdMissingVariableError( - msg, - ) + raise AristaAvdInvalidInputsError(msg) # self.shared_utils.inband_mgmt_vrf returns None for the default VRF, but here we need "default" to avoid duplicates. vrf = self.shared_utils.inband_mgmt_vrf or "default" diff --git a/python-avd/pyavd/_eos_designs/structured_config/inband_management/__init__.py b/python-avd/pyavd/_eos_designs/structured_config/inband_management/__init__.py index 91a45f49d88..0121c2bf4ce 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/inband_management/__init__.py +++ b/python-avd/pyavd/_eos_designs/structured_config/inband_management/__init__.py @@ -7,7 +7,7 @@ from ipaddress import ip_network from pyavd._eos_designs.avdfacts import AvdFacts -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import get, strip_empties_from_dict from pyavd.j2filters import natural_sort @@ -107,7 +107,7 @@ def ip_virtual_router_mac_address(self) -> str | None: if self.shared_utils.virtual_router_mac_address is None: msg = "'virtual_router_mac_address' must be set for inband management parent." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) return str(self.shared_utils.virtual_router_mac_address).lower() @cached_property diff --git a/python-avd/pyavd/_eos_designs/structured_config/metadata/cv_pathfinder.py b/python-avd/pyavd/_eos_designs/structured_config/metadata/cv_pathfinder.py index 3d8f64bdbff..16fa4d6c95a 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/metadata/cv_pathfinder.py +++ b/python-avd/pyavd/_eos_designs/structured_config/metadata/cv_pathfinder.py @@ -99,7 +99,7 @@ def _metadata_regions(self: AvdStructuredConfigMetadata) -> list: self._hostvars, "cv_pathfinder_regions", required=True, - org_key="'cv_pathfinder_regions' key must be set when 'wan_mode' is 'cv-pathfinder'.", + custom_error_msg="'cv_pathfinder_regions' key must be set when 'wan_mode' is 'cv-pathfinder'.", ) return [ { diff --git a/python-avd/pyavd/_eos_designs/structured_config/network_services/router_bgp.py b/python-avd/pyavd/_eos_designs/structured_config/network_services/router_bgp.py index 18101c777ee..a64ab225e85 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/network_services/router_bgp.py +++ b/python-avd/pyavd/_eos_designs/structured_config/network_services/router_bgp.py @@ -9,7 +9,7 @@ from re import fullmatch as re_fullmatch from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import AvdStringFormatter, append_if_not_duplicate, default, get, get_item, merge, strip_empties_from_dict from pyavd.j2filters import list_compress, natural_sort @@ -526,9 +526,7 @@ def _get_evpn_vlan_bundle(self: AvdStructuredConfigNetworkServices, vlan: dict, "The 'evpn_vlan_bundle' of the svis/l2vlans must be defined in the common 'evpn_vlan_bundles' setting. First occurrence seen for svi/l2vlan" f" {vlan['id']} in Tenant '{vlan['tenant']}' and evpn_vlan_bundle '{vlan['evpn_vlan_bundle']}'." ) - raise AristaAvdMissingVariableError( - msg, - ) + raise AristaAvdInvalidInputsError(msg) return evpn_vlan_bundle def _get_svi_l2vlan_bundle(self: AvdStructuredConfigNetworkServices, evpn_vlan_bundle: dict, tenant: dict, vlans: list) -> dict | None: @@ -706,9 +704,7 @@ def get_vlan_mac_vrf_id(self: AvdStructuredConfigNetworkServices, vlan: dict, te "'rt_override' or 'vni_override' or 'mac_vrf_id_base' or 'mac_vrf_vni_base' must be set. " f"Unable to set EVPN RD/RT for vlan {vlan['id']} in Tenant '{vlan['tenant']}'" ) - raise AristaAvdMissingVariableError( - msg, - ) + raise AristaAvdInvalidInputsError(msg) return mac_vrf_id_base + int(vlan["id"]) def get_vlan_mac_vrf_vni(self: AvdStructuredConfigNetworkServices, vlan: dict, tenant: dict) -> int: @@ -718,9 +714,7 @@ def get_vlan_mac_vrf_vni(self: AvdStructuredConfigNetworkServices, vlan: dict, t "'rt_override' or 'vni_override' or 'mac_vrf_id_base' or 'mac_vrf_vni_base' must be set. " f"Unable to set EVPN RD/RT for vlan {vlan['id']} in Tenant '{vlan['tenant']}'" ) - raise AristaAvdMissingVariableError( - msg, - ) + raise AristaAvdInvalidInputsError(msg) return mac_vrf_vni_base + int(vlan["id"]) def get_vlan_rd(self: AvdStructuredConfigNetworkServices, vlan: dict, tenant: dict) -> str: @@ -939,7 +933,7 @@ def _router_bgp_vpws(self: AvdStructuredConfigNetworkServices) -> list[dict] | N ) if pseudowires: - rt_base = get(tenant, "pseudowire_rt_base", required=True, org_key=f"'pseudowire_rt_base' under Tenant '{tenant['name']}") + rt_base = get(tenant, "pseudowire_rt_base", required=True, custom_error_msg=f"'pseudowire_rt_base' under Tenant '{tenant['name']} is required.") rd = f"{self.shared_utils.overlay_rd_type_admin_subfield}:{rt_base}" rt = f"{self._rt_admin_subfield or rt_base}:{rt_base}" vpws.append( diff --git a/python-avd/pyavd/_eos_designs/structured_config/network_services/router_ospf.py b/python-avd/pyavd/_eos_designs/structured_config/network_services/router_ospf.py index 8ae4b506748..6faead71e37 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/network_services/router_ospf.py +++ b/python-avd/pyavd/_eos_designs/structured_config/network_services/router_ospf.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import append_if_not_duplicate, default, get from .utils import UtilsMixin @@ -59,7 +59,7 @@ def router_ospf(self: AvdStructuredConfigNetworkServices) -> dict | None: process_id = default(get(vrf, "ospf.process_id"), vrf.get("vrf_id")) if not process_id: msg = f"'ospf.process_id' or 'vrf_id' under vrf '{vrf['name']}" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) process = { "id": process_id, diff --git a/python-avd/pyavd/_eos_designs/structured_config/network_services/utils.py b/python-avd/pyavd/_eos_designs/structured_config/network_services/utils.py index a454e939b79..e1d40ff0bed 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/network_services/utils.py +++ b/python-avd/pyavd/_eos_designs/structured_config/network_services/utils.py @@ -7,7 +7,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import default, get, get_item from pyavd.j2filters import natural_sort @@ -157,9 +157,7 @@ def _mlag_ibgp_peering_vlan_vrf(self: AvdStructuredConfigNetworkServices, vrf: d vrf_id = vrf.get("vrf_id", vrf.get("vrf_vni")) if vrf_id is None: msg = f"Unable to assign MLAG VRF Peering VLAN for vrf {vrf['name']}.Set either 'mlag_ibgp_peering_vlan' or 'vrf_id' or 'vrf_vni' on the VRF" - raise AristaAvdMissingVariableError( - msg, - ) + raise AristaAvdInvalidInputsError(msg) vlan_id = base_vlan + int(vrf_id) - 1 return vlan_id diff --git a/python-avd/pyavd/_eos_designs/structured_config/network_services/utils_wan.py b/python-avd/pyavd/_eos_designs/structured_config/network_services/utils_wan.py index 87e1e6f9bdb..7e7178a3e03 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/network_services/utils_wan.py +++ b/python-avd/pyavd/_eos_designs/structured_config/network_services/utils_wan.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING, Literal -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import get, get_all, get_ip_from_ip_prefix, get_item from pyavd._utils.password_utils.password import simple_7_encrypt from pyavd.j2filters import natural_sort, range_expand @@ -37,7 +37,7 @@ def _filtered_wan_vrfs(self: AvdStructuredConfigNetworkServices) -> list: vrf, "wan_vni", required=True, - org_key=f"Required `wan_vni` is missing for VRF {vrf_name} under `wan_virtual_topologies.vrfs`.", + custom_error_msg=f"Required `wan_vni` is missing for VRF {vrf_name} under `wan_virtual_topologies.vrfs`.", ), } @@ -172,10 +172,10 @@ def _update_policy_match_statements(self: AvdStructuredConfigNetworkServices, po application_virtual_topology, "id", required=self.shared_utils.is_cv_pathfinder_router, - org_key=( + custom_error_msg=( f"Missing mandatory `id` in " f"`wan_virtual_topologies.policies[{policy['name']}].application_virtual_topologies[{application_profile}]` " - "when `wan_mode` is 'cv-pathfinder" + "when `wan_mode` is 'cv-pathfinder." ), ) @@ -195,7 +195,7 @@ def _update_policy_match_statements(self: AvdStructuredConfigNetworkServices, po policy, "default_virtual_topology", required=True, - org_key=f"wan_virtual_topologies.policies[{policy['profile_prefix']}].default_virtual_toplogy", + custom_error_msg=f"wan_virtual_topologies.policies[{policy['profile_prefix']}].default_virtual_toplogy.", ) # Separating default_match as it is used differently default_match = None @@ -211,7 +211,7 @@ def _update_policy_match_statements(self: AvdStructuredConfigNetworkServices, po default_virtual_topology, "path_groups", required=True, - org_key=f"Either 'drop_unmatched' or 'path_groups' must be set under '{context_path}'.", + custom_error_msg=f"Either 'drop_unmatched' or 'path_groups' must be set under '{context_path}'.", ) load_balance_policy_name = self.shared_utils.generate_lb_policy_name(name) load_balance_policy = self._generate_wan_load_balance_policy(load_balance_policy_name, default_virtual_topology, context_path) @@ -698,9 +698,7 @@ def get_direct_internet_exit_connections(self: AvdStructuredConfigNetworkService f"{wan_interface['name']} peer_ip needs to be set. When using wan interface " "for direct type internet exit, peer_ip is used for nexthop, and connectivity monitoring." ) - raise AristaAvdMissingVariableError( - msg, - ) + raise AristaAvdInvalidInputsError(msg) # wan interface ip will be used for acl, hence raise error if ip is not available if (ip_address := wan_interface.get("ip_address")) == "dhcp" and not (ip_address := wan_interface.get("dhcp_ip")): @@ -708,9 +706,7 @@ def get_direct_internet_exit_connections(self: AvdStructuredConfigNetworkService f"{wan_interface['name']} 'dhcp_ip' needs to be set. When using WAN interface for 'direct' type Internet exit, " "'dhcp_ip' is used in the NAT ACL." ) - raise AristaAvdMissingVariableError( - msg, - ) + raise AristaAvdInvalidInputsError(msg) sanitized_interface_name = self.shared_utils.sanitize_interface_name(wan_interface["name"]) connections.append( @@ -752,7 +748,7 @@ def get_zscaler_internet_exit_connections(self: AvdStructuredConfigNetworkServic wan_interface, "peer_ip", required=True, - org_key=f"The configured internet-exit policy requires `peer_ip` configured under the WAN Interface {wan_interface['name']}", + custom_error_msg=f"The configured internet-exit policy requires `peer_ip` configured under the WAN Interface {wan_interface['name']}.", ), # Accepting SonarLint issue: The URL is just for verifying connectivity. No data is passed. "monitor_url": f"http://gateway.{cloud_name}.net/vpntest", # NOSONAR @@ -764,9 +760,7 @@ def get_zscaler_internet_exit_connections(self: AvdStructuredConfigNetworkServic f"{wan_interface['name']}.cv_pathfinder_internet_exit.policies[{internet_exit_policy['name']}]." "tunnel_interface_numbers needs to be set, when using wan interface for zscaler type internet exit." ) - raise AristaAvdMissingVariableError( - msg, - ) + raise AristaAvdInvalidInputsError(msg) tunnel_id_range = range_expand(tunnel_interface_numbers) diff --git a/python-avd/pyavd/_eos_designs/structured_config/network_services/utils_zscaler.py b/python-avd/pyavd/_eos_designs/structured_config/network_services/utils_zscaler.py index 20155d7bbd9..0e349d78487 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/network_services/utils_zscaler.py +++ b/python-avd/pyavd/_eos_designs/structured_config/network_services/utils_zscaler.py @@ -51,13 +51,13 @@ async def _generate_zscaler_endpoints(self: AvdStructuredConfigNetworkServices) TODO: Add support for cv_verify_certs """ context = "The WAN Internet-exit integration with Zscaler fetches information from CloudVision" - cv_server = get(self._hostvars, "cv_server", required=True, org_key=f"{context} and requires 'cv_server' to be set.") - cv_token = get(self._hostvars, "cv_token", required=True, org_key=f"{context} and requires 'cv_server' to be set.") + cv_server = get(self._hostvars, "cv_server", required=True, custom_error_msg=f"{context} and requires 'cv_server' to be set.") + cv_token = get(self._hostvars, "cv_token", required=True, custom_error_msg=f"{context} and requires 'cv_server' to be set.") wan_site_location = get( self.shared_utils.wan_site, "location", required=True, - org_key=( + custom_error_msg=( f"{context} and requires 'cv_pathfinder_regions[name={self.shared_utils.wan_region['name']}]" f".sites[name={self.shared_utils.wan_site['name']}].location' to be set." ), diff --git a/python-avd/pyavd/_eos_designs/structured_config/network_services/vlan_interfaces.py b/python-avd/pyavd/_eos_designs/structured_config/network_services/vlan_interfaces.py index d8e044697f4..6541380aa02 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/network_services/vlan_interfaces.py +++ b/python-avd/pyavd/_eos_designs/structured_config/network_services/vlan_interfaces.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import append_if_not_duplicate, default, get, strip_empties_from_dict from pyavd.api.interface_descriptions import InterfaceDescriptionData @@ -81,7 +81,7 @@ def _check_virtual_router_mac_address(vlan_interface_config: dict, variables: li if any(vlan_interface_config.get(var) for var in variables) and self.shared_utils.virtual_router_mac_address is None: quoted_vars = [f"'{var}'" for var in variables] msg = f"'virtual_router_mac_address' must be set for node '{self.shared_utils.hostname}' when using {' or '.join(quoted_vars)} under 'svi'" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) interface_name = f"Vlan{svi['id']}" vlan_interface_config = { @@ -123,7 +123,7 @@ def _check_virtual_router_mac_address(vlan_interface_config: dict, variables: li f"No vtep_diagnostic loopback defined on VRF '{vrf['name']}' in Tenant '{svi['tenant']}'." "This is required when 'l3_multicast' is enabled on the VRF and ip_address_virtual is used on an SVI in that VRF." ) - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) pim_config_ipv4["local_interface"] = f"Loopback{vrf_diagnostic_loopback}" if pim_config_ipv4: 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 6e931e88353..e8ef3792d2a 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 @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING, NoReturn -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import append_if_not_duplicate, default, get, get_item, unique from pyavd.j2filters import natural_sort, range_expand @@ -161,7 +161,7 @@ def _get_vxlan_interface_config_for_vrf(self: AvdStructuredConfigNetworkServices "use the VRF filter 'deny_vrfs' under the node settings." ) wan_vrf = get_item(self._filtered_wan_vrfs, "name", vrf_name, required=True, custom_error_msg=error_message) - vni = get(wan_vrf, "wan_vni", required=True, org_key=error_message) + vni = get(wan_vrf, "wan_vni", required=True, custom_error_msg=error_message) else: vni = default( vrf.get("vrf_vni"), @@ -186,7 +186,7 @@ def _get_vxlan_interface_config_for_vrf(self: AvdStructuredConfigNetworkServices 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']}", + custom_error_msg=f"'evpn_l3_multicast.evpn_underlay_l3_multicast_group_ipv4_pool' for Tenant: {tenant['name']} is required.", ) 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( @@ -228,7 +228,9 @@ def _get_vxlan_interface_config_for_vlan(self: AvdStructuredConfigNetworkService if (vni_override := vlan.get("vni_override")) is not None: vxlan_interface_vlan["vni"] = int(vni_override) else: - mac_vrf_vni_base = int(get(tenant, "mac_vrf_vni_base", required=True, org_key=f"'mac_vrf_vni_base' for Tenant: {tenant['name']}")) + mac_vrf_vni_base = int( + get(tenant, "mac_vrf_vni_base", required=True, custom_error_msg=f"'mac_vrf_vni_base' for Tenant: {tenant['name']} is required.") + ) vxlan_interface_vlan["vni"] = mac_vrf_vni_base + vlan_id vlan_evpn_l2_multicast_enabled = ( @@ -239,7 +241,7 @@ def _get_vxlan_interface_config_for_vlan(self: AvdStructuredConfigNetworkService tenant, "evpn_l2_multicast.underlay_l2_multicast_group_ipv4_pool", required=True, - org_key=f"'evpn_l2_multicast.underlay_l2_multicast_group_ipv4_pool' for Tenant: {tenant['name']}", + custom_error_msg=f"'evpn_l2_multicast.underlay_l2_multicast_group_ipv4_pool' for Tenant: {tenant['name']} is required.", ) underlay_l2_multicast_group_ipv4_pool_offset = get(tenant, "evpn_l2_multicast.underlay_l2_multicast_group_ipv4_pool_offset", default=0) vxlan_interface_vlan["multicast_group"] = self.shared_utils.ip_addressing.evpn_underlay_l2_multicast_group( @@ -279,7 +281,7 @@ def _overlay_her_flood_lists(self: AvdStructuredConfigNetworkServices) -> dict[l if overlay_her_flood_list_scope == "dc" and self.shared_utils.dc_name is None: msg = "'dc_name' is required with 'overlay_her_flood_list_scope: dc'" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) for peer in self.shared_utils.all_fabric_devices: if peer == self.shared_utils.hostname: diff --git a/python-avd/pyavd/_eos_designs/structured_config/overlay/cvx.py b/python-avd/pyavd/_eos_designs/structured_config/overlay/cvx.py index e9225f1b529..3f5db9fcde7 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/overlay/cvx.py +++ b/python-avd/pyavd/_eos_designs/structured_config/overlay/cvx.py @@ -37,7 +37,7 @@ def cvx(self: AvdStructuredConfigOverlay) -> dict | None: continue peer_switch_facts = self.shared_utils.get_peer_facts(overlay_cvx_server, required=True) - cvx_server_ip = get(peer_switch_facts, "mgmt_ip", required=True, org_key=f"'mgmt_ip' for CVX Server {overlay_cvx_server}") + cvx_server_ip = get(peer_switch_facts, "mgmt_ip", required=True, custom_error_msg=f"'mgmt_ip' for CVX Server {overlay_cvx_server} is required.") peer_hosts.append(get_ip_from_ip_prefix(cvx_server_ip)) return { diff --git a/python-avd/pyavd/_eos_designs/structured_config/overlay/management_cvx.py b/python-avd/pyavd/_eos_designs/structured_config/overlay/management_cvx.py index a7700974af4..e196648c977 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/overlay/management_cvx.py +++ b/python-avd/pyavd/_eos_designs/structured_config/overlay/management_cvx.py @@ -30,7 +30,7 @@ def management_cvx(self: AvdStructuredConfigOverlay) -> dict | None: overlay_cvx_servers = get(self._hostvars, "overlay_cvx_servers", required=True) for overlay_cvx_server in overlay_cvx_servers: peer_switch_facts = self.shared_utils.get_peer_facts(overlay_cvx_server, required=True) - cvx_server_ip = get(peer_switch_facts, "mgmt_ip", required=True, org_key=f"'mgmt_ip' for CVX Server {overlay_cvx_server}") + cvx_server_ip = get(peer_switch_facts, "mgmt_ip", required=True, custom_error_msg=f"'mgmt_ip' for CVX Server {overlay_cvx_server} is required.") server_hosts.append(get_ip_from_ip_prefix(cvx_server_ip)) return { diff --git a/python-avd/pyavd/_eos_designs/structured_config/overlay/utils.py b/python-avd/pyavd/_eos_designs/structured_config/overlay/utils.py index 3a815bf76ee..e8f3e01678c 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/overlay/utils.py +++ b/python-avd/pyavd/_eos_designs/structured_config/overlay/utils.py @@ -261,7 +261,7 @@ def _append_peer(self: AvdStructuredConfigOverlay, peers_dict: dict, peer_name: peer_facts, "overlay.peering_address", required=True, - org_key=f"switch.overlay.peering_address for {peer_name}", + custom_error_msg=f"switch.overlay.peering_address for {peer_name} is required.", ), "overlay_peering_interface": "Loopback0", } diff --git a/python-avd/pyavd/_eos_designs/structured_config/underlay/loopback_interfaces.py b/python-avd/pyavd/_eos_designs/structured_config/underlay/loopback_interfaces.py index bdb28336759..ba3da306207 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/underlay/loopback_interfaces.py +++ b/python-avd/pyavd/_eos_designs/structured_config/underlay/loopback_interfaces.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import default, get from pyavd.api.interface_descriptions import InterfaceDescriptionData @@ -110,6 +110,6 @@ def loopback_interfaces(self: AvdStructuredConfigUnderlay) -> list | None: def _node_sid(self: AvdStructuredConfigUnderlay) -> str: if self.shared_utils.id is None: msg = f"'id' is not set on '{self.shared_utils.hostname}' and is required to set node SID" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) node_sid_base = int(get(self.shared_utils.switch_data_combined, "node_sid_base", 0)) return self.shared_utils.id + node_sid_base diff --git a/python-avd/pyavd/_eos_designs/structured_config/underlay/router_isis.py b/python-avd/pyavd/_eos_designs/structured_config/underlay/router_isis.py index d0b96da35c6..4822979e142 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/underlay/router_isis.py +++ b/python-avd/pyavd/_eos_designs/structured_config/underlay/router_isis.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError from pyavd._utils import get from .utils import UtilsMixin @@ -83,11 +83,11 @@ def _isis_net(self: AvdStructuredConfigUnderlay) -> str | None: f"'isis_system_id_prefix' is required when 'isis_system_id_format' is set to 'node_id'." f" 'isis_system_id_prefix' was not set for '{self.shared_utils.hostname}'" ) - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) if self.shared_utils.id is None: msg = f"'id' is not set on '{self.shared_utils.hostname}' and is required to set ISIS NET address using the node ID" - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) system_id = f"{isis_system_id_prefix}.{self.shared_utils.id:04d}" else: system_id = self.ipv4_to_isis_system_id(self.shared_utils.router_id) diff --git a/python-avd/pyavd/_eos_designs/structured_config/underlay/static_routes.py b/python-avd/pyavd/_eos_designs/structured_config/underlay/static_routes.py index 4a20e1d1f55..e4b6c563eeb 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/underlay/static_routes.py +++ b/python-avd/pyavd/_eos_designs/structured_config/underlay/static_routes.py @@ -41,7 +41,7 @@ def static_routes(self: AvdStructuredConfigUnderlay) -> list[dict] | None: l3_interface, "peer_ip", required=True, - org_key=f"Cannot set a static_route route for interface {interface_name} because 'peer_ip' is missing", + custom_error_msg=f"Cannot set a static_route route for interface {interface_name} because 'peer_ip' is missing.", ) static_routes.extend( diff --git a/python-avd/pyavd/_errors/__init__.py b/python-avd/pyavd/_errors/__init__.py index 0bb246f9f51..f8b6452c42a 100644 --- a/python-avd/pyavd/_errors/__init__.py +++ b/python-avd/pyavd/_errors/__init__.py @@ -12,17 +12,24 @@ def _json_path_to_string(self, json_path: list[str | int]) -> str: path = "" for index, elem in enumerate(json_path): if isinstance(elem, int): - path += "[" + str(elem) + "]" + path += f"[{elem}]" else: if index == 0: path += elem continue - path += "." + elem + path += f".{elem}" return path +class AristaAvdInvalidInputsError(AristaAvdError): + def __init__(self, message: str) -> None: + super().__init__(message) + + class AristaAvdMissingVariableError(AristaAvdError): - pass + def __init__(self, variable: str | None = None) -> None: + self.message = f"'{variable}' is required but was not found." + super().__init__(self.message) class AvdSchemaError(AristaAvdError): diff --git a/python-avd/pyavd/_utils/get.py b/python-avd/pyavd/_utils/get.py index ba5fae75d55..43ca9a4b122 100644 --- a/python-avd/pyavd/_utils/get.py +++ b/python-avd/pyavd/_utils/get.py @@ -3,10 +3,18 @@ # that can be found in the LICENSE file. from typing import Any -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError, AristaAvdMissingVariableError -def get(dictionary: dict, key: str, default: Any = None, required: bool = False, org_key: str | None = None, separator: str = ".") -> Any: +def get( + dictionary: dict, + key: str, + default: Any = None, + required: bool = False, + org_key: str | None = None, + separator: str = ".", + custom_error_msg: str | None = None, +) -> Any: """ Get a value from a dictionary or nested dictionaries. @@ -28,6 +36,8 @@ def get(dictionary: dict, key: str, default: Any = None, required: bool = False, separator: str String to use as the separator parameter in the split function. Useful in cases when the key can contain variables with "." inside (e.g. hostnames) + custom_error_msg: str + Custom error message to raise when required is True and the value is not found Returns: ------- @@ -45,17 +55,25 @@ def get(dictionary: dict, key: str, default: Any = None, required: bool = False, value = dictionary.get(keys[0]) if value is None: if required is True: + if custom_error_msg: + raise AristaAvdInvalidInputsError(custom_error_msg) raise AristaAvdMissingVariableError(org_key) return default if len(keys) > 1: - return get(value, separator.join(keys[1:]), default=default, required=required, org_key=org_key, separator=separator) + return get(value, separator.join(keys[1:]), default=default, required=required, org_key=org_key, separator=separator, custom_error_msg=custom_error_msg) return value def get_v2( - dict_or_object: dict | object, key_or_attribute: str, default: Any = None, required: bool = False, org_key: str | None = None, separator: str = "." + dict_or_object: dict | object, + key_or_attribute: str, + default: Any = None, + required: bool = False, + org_key: str | None = None, + separator: str = ".", + custom_error_msg: str | None = None, ) -> Any: """ Get a value from a dictionary or object or nested dictionaries and objects. @@ -78,6 +96,8 @@ def get_v2( separator: str String to use as the separator parameter in the split function. Useful in cases when the key can contain variables with "." inside (e.g. hostnames) + custom_error_msg: str + Custom error message to raise when required is True and the value is not found Returns: ------- @@ -96,10 +116,14 @@ def get_v2( if value is None: if required is True: + if custom_error_msg: + raise AristaAvdInvalidInputsError(custom_error_msg) raise AristaAvdMissingVariableError(org_key) return default if len(keys) > 1: - return get_v2(value, separator.join(keys[1:]), default=default, required=required, org_key=org_key, separator=separator) + return get_v2( + value, separator.join(keys[1:]), default=default, required=required, org_key=org_key, separator=separator, custom_error_msg=custom_error_msg + ) return value diff --git a/python-avd/pyavd/_utils/get_item.py b/python-avd/pyavd/_utils/get_item.py index 7ac26439cd6..f5457df3328 100644 --- a/python-avd/pyavd/_utils/get_item.py +++ b/python-avd/pyavd/_utils/get_item.py @@ -3,7 +3,7 @@ # that can be found in the LICENSE file. from typing import Any -from pyavd._errors import AristaAvdMissingVariableError +from pyavd._errors import AristaAvdInvalidInputsError, AristaAvdMissingVariableError def get_item( @@ -58,7 +58,9 @@ def get_item( if (not isinstance(list_of_dicts, list)) or list_of_dicts == [] or value is None or key is None: if required is True: - raise AristaAvdMissingVariableError(custom_error_msg or var_name) + if custom_error_msg: + raise AristaAvdInvalidInputsError(custom_error_msg) + raise AristaAvdMissingVariableError(var_name) return default for list_item in list_of_dicts: @@ -71,5 +73,7 @@ def get_item( # No Match if required is True: - raise AristaAvdMissingVariableError(custom_error_msg or var_name) + if custom_error_msg: + raise AristaAvdInvalidInputsError(custom_error_msg) + raise AristaAvdMissingVariableError(var_name) return default diff --git a/python-avd/pyavd/_utils/load_python_class.py b/python-avd/pyavd/_utils/load_python_class.py index 4cf6a055e0b..54874483062 100644 --- a/python-avd/pyavd/_utils/load_python_class.py +++ b/python-avd/pyavd/_utils/load_python_class.py @@ -5,7 +5,7 @@ import importlib -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError def load_python_class(module_path: str, class_name: str, parent_class: type | None = None) -> type: @@ -37,10 +37,10 @@ def load_python_class(module_path: str, class_name: str, parent_class: type | No """ if not module_path: msg = "Cannot load a python class without the module_path set." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) if not class_name: msg = "Cannot load a python class without the class_name set." - raise AristaAvdMissingVariableError(msg) + raise AristaAvdInvalidInputsError(msg) try: cls = getattr(importlib.import_module(module_path), class_name) diff --git a/python-avd/pyavd/api/ip_addressing/utils.py b/python-avd/pyavd/api/ip_addressing/utils.py index 2b2bed7c57d..ca6191fd470 100644 --- a/python-avd/pyavd/api/ip_addressing/utils.py +++ b/python-avd/pyavd/api/ip_addressing/utils.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING -from pyavd._errors import AristaAvdError, AristaAvdMissingVariableError +from pyavd._errors import AristaAvdError, AristaAvdInvalidInputsError from pyavd._utils import get from pyavd.j2filters import range_expand @@ -24,15 +24,15 @@ class UtilsMixin: @cached_property def _mlag_primary_id(self: AvdIpAddressing) -> int: if self.shared_utils.mlag_switch_ids is None or self.shared_utils.mlag_switch_ids.get("primary") is None: - msg = "'mlag_switch_ids' is required to calculate MLAG IP addresses" - raise AristaAvdMissingVariableError(msg) + msg = "'mlag_switch_ids' is required to calculate MLAG IP addresses." + raise AristaAvdInvalidInputsError(msg) return self.shared_utils.mlag_switch_ids["primary"] @cached_property def _mlag_secondary_id(self: AvdIpAddressing) -> int: if self.shared_utils.mlag_switch_ids is None or self.shared_utils.mlag_switch_ids.get("secondary") is None: - msg = "'mlag_switch_ids' is required to calculate MLAG IP addresses" - raise AristaAvdMissingVariableError(msg) + msg = "'mlag_switch_ids' is required to calculate MLAG IP addresses." + raise AristaAvdInvalidInputsError(msg) return self.shared_utils.mlag_switch_ids["secondary"] @cached_property @@ -66,15 +66,15 @@ def _mlag_peer_l3_ipv4_pool(self: AvdIpAddressing) -> str: @cached_property def _uplink_ipv4_pool(self: AvdIpAddressing) -> str: if self.shared_utils.uplink_ipv4_pool is None: - msg = "'uplink_ipv4_pool' is required to calculate uplink IP addresses" - raise AristaAvdMissingVariableError(msg) + msg = "'uplink_ipv4_pool' is required to calculate uplink IP addresses." + raise AristaAvdInvalidInputsError(msg) return self.shared_utils.uplink_ipv4_pool @cached_property def _id(self: AvdIpAddressing) -> int: if self.shared_utils.id is None: - msg = "'id' is required to calculate IP addresses" - raise AristaAvdMissingVariableError(msg) + msg = "'id' is required to calculate IP addresses." + raise AristaAvdInvalidInputsError(msg) return self.shared_utils.id @cached_property @@ -186,8 +186,8 @@ def _get_p2p_ipv4_pool_and_offset(self: AvdIpAddressing, uplink_switch_index: in raise AristaAvdError(msg) if uplink_pool is None and downlink_pool is None: - msg = "Unable to assign IPs for uplinks. Either 'uplink_ipv4_pool' on this switch or 'downlink_pools' on all the uplink switches" - raise AristaAvdMissingVariableError(msg) + msg = "Unable to assign IPs for uplinks. Either 'uplink_ipv4_pool' on this switch or 'downlink_pools' on all the uplink switches must be set." + raise AristaAvdInvalidInputsError(msg) if uplink_pool is not None: return (uplink_pool, uplink_offset)